1#[cfg(not(feature = "std"))]
2extern crate alloc;
3
4use crate::{
5 DomainInherentExtrinsic, DomainInherentExtrinsicData, DomainStorageKeyRequest,
6 StatelessDomainRuntimeCall,
7};
8#[cfg(not(feature = "std"))]
9use alloc::vec::Vec;
10use domain_block_preprocessor::stateless_runtime::StatelessRuntime;
11use domain_runtime_primitives::{
12 BlockNumber, CheckExtrinsicsValidityError, CHECK_EXTRINSICS_AND_DO_PRE_DISPATCH_METHOD_NAME,
13};
14use hash_db::{HashDB, Hasher};
15use parity_scale_codec::{Codec, Decode, Encode};
16use sc_client_api::execution_extensions::ExtensionsFactory;
17use sc_client_api::BlockBackend;
18use sc_executor::RuntimeVersionOf;
19use sp_api::ProvideRuntimeApi;
20use sp_blockchain::HeaderBackend;
21use sp_core::traits::{CallContext, CodeExecutor, FetchRuntimeCode, RuntimeCode};
22use sp_core::H256;
23use sp_domains::{BundleProducerElectionApi, DomainsApi, ExtrinsicDigest};
24use sp_externalities::Extensions;
25use sp_messenger::MessengerApi;
26use sp_runtime::traits::{Block as BlockT, Hash as HashT, HashingFor, NumberFor};
27use sp_runtime::OpaqueExtrinsic;
28use sp_state_machine::{LayoutV1, OverlayedChanges, StateMachine, TrieBackend, TrieBackendBuilder};
29use sp_trie::{MemoryDB, StorageProof};
30use sp_weights::Weight;
31use std::borrow::Cow;
32use std::marker::PhantomData;
33use std::sync::Arc;
34use subspace_runtime_primitives::Balance;
35
36struct DomainRuntimeCodeFetcher(Vec<u8>);
37
38impl FetchRuntimeCode for DomainRuntimeCodeFetcher {
39 fn fetch_runtime_code(&self) -> Option<Cow<[u8]>> {
40 Some(Cow::Borrowed(self.0.as_ref()))
41 }
42}
43
44pub trait FraudProofHostFunctions: Send + Sync {
46 fn derive_bundle_digest(
48 &self,
49 domain_runtime_code: Vec<u8>,
50 bundle_body: Vec<OpaqueExtrinsic>,
51 ) -> Option<H256>;
52
53 fn execution_proof_check(
55 &self,
56 domain_block_id: (BlockNumber, H256),
57 pre_state_root: H256,
58 encoded_proof: Vec<u8>,
60 execution_method: &str,
61 call_data: &[u8],
62 domain_runtime_code: Vec<u8>,
63 ) -> Option<Vec<u8>>;
64
65 fn check_extrinsics_in_single_context(
66 &self,
67 domain_runtime_code: Vec<u8>,
68 domain_block_id: (BlockNumber, H256),
69 domain_block_state_root: H256,
70 bundle_extrinsics: Vec<OpaqueExtrinsic>,
71 encoded_proof: Vec<u8>,
73 ) -> Option<Option<u32>>;
74
75 fn construct_domain_inherent_extrinsic(
76 &self,
77 domain_runtime_code: Vec<u8>,
78 domain_inherent_extrinsic_data: DomainInherentExtrinsicData,
79 ) -> Option<DomainInherentExtrinsic>;
80
81 fn domain_storage_key(
82 &self,
83 domain_runtime_code: Vec<u8>,
84 req: DomainStorageKeyRequest,
85 ) -> Option<Vec<u8>>;
86
87 fn domain_runtime_call(
88 &self,
89 domain_runtime_code: Vec<u8>,
90 call: StatelessDomainRuntimeCall,
91 ) -> Option<bool>;
92
93 fn bundle_weight(
94 &self,
95 domain_runtime_code: Vec<u8>,
96 bundle_body: Vec<OpaqueExtrinsic>,
97 ) -> Option<Weight>;
98
99 fn extract_xdm_mmr_proof(
100 &self,
101 domain_runtime_code: Vec<u8>,
102 opaque_extrinsic: Vec<u8>,
103 ) -> Option<Option<Vec<u8>>>;
104}
105
106sp_externalities::decl_extension! {
107 pub struct FraudProofExtension(std::sync::Arc<dyn FraudProofHostFunctions>);
109}
110
111impl FraudProofExtension {
112 pub fn new(inner: std::sync::Arc<dyn FraudProofHostFunctions>) -> Self {
114 Self(inner)
115 }
116}
117
118pub struct FraudProofHostFunctionsImpl<Block, Client, DomainBlock, Executor, EFC> {
120 consensus_client: Arc<Client>,
121 domain_executor: Arc<Executor>,
122 domain_extensions_factory_creator: EFC,
123 _phantom: PhantomData<(Block, DomainBlock)>,
124}
125
126impl<Block, Client, DomainBlock, Executor, EFC>
127 FraudProofHostFunctionsImpl<Block, Client, DomainBlock, Executor, EFC>
128{
129 pub fn new(
130 consensus_client: Arc<Client>,
131 domain_executor: Arc<Executor>,
132 domain_extensions_factory_creator: EFC,
133 ) -> Self {
134 FraudProofHostFunctionsImpl {
135 consensus_client,
136 domain_executor,
137 domain_extensions_factory_creator,
138 _phantom: Default::default(),
139 }
140 }
141}
142
143impl<Block, Client, DomainBlock, Executor, EFC> FraudProofHostFunctions
144 for FraudProofHostFunctionsImpl<Block, Client, DomainBlock, Executor, EFC>
145where
146 Block: BlockT,
147 Block::Hash: From<H256>,
148 DomainBlock: BlockT,
149 DomainBlock::Hash: From<H256> + Into<H256>,
150 NumberFor<DomainBlock>: From<BlockNumber>,
151 Client: BlockBackend<Block> + HeaderBackend<Block> + ProvideRuntimeApi<Block>,
152 Client::Api: DomainsApi<Block, DomainBlock::Header>
153 + BundleProducerElectionApi<Block, Balance>
154 + MessengerApi<Block, NumberFor<Block>, Block::Hash>,
155 Executor: CodeExecutor + RuntimeVersionOf,
156 EFC: Fn(Arc<Client>, Arc<Executor>) -> Box<dyn ExtensionsFactory<DomainBlock>> + Send + Sync,
157{
158 fn derive_bundle_digest(
159 &self,
160 domain_runtime_code: Vec<u8>,
161 bundle_body: Vec<OpaqueExtrinsic>,
162 ) -> Option<H256> {
163 let mut extrinsics = Vec::with_capacity(bundle_body.len());
164 for opaque_extrinsic in bundle_body {
165 let ext =
166 DomainBlock::Extrinsic::decode(&mut opaque_extrinsic.encode().as_slice()).ok()?;
167 extrinsics.push(ext);
168 }
169
170 let domain_stateless_runtime = StatelessRuntime::<Block, DomainBlock, _>::new(
171 self.domain_executor.clone(),
172 domain_runtime_code.into(),
173 );
174
175 let ext_signers: Vec<_> = domain_stateless_runtime
176 .extract_signer(extrinsics)
177 .ok()?
178 .into_iter()
179 .map(|(signer, tx)| {
180 (
181 signer,
182 ExtrinsicDigest::new::<LayoutV1<HashingFor<DomainBlock>>>(tx.encode()),
183 )
184 })
185 .collect();
186
187 Some(HashingFor::<DomainBlock>::hash_of(&ext_signers).into())
188 }
189
190 fn execution_proof_check(
191 &self,
192 domain_block_id: (BlockNumber, H256),
193 pre_state_root: H256,
194 encoded_proof: Vec<u8>,
195 execution_method: &str,
196 call_data: &[u8],
197 domain_runtime_code: Vec<u8>,
198 ) -> Option<Vec<u8>> {
199 let proof: StorageProof = Decode::decode(&mut encoded_proof.as_ref()).ok()?;
200
201 let domain_runtime_code_fetcher = DomainRuntimeCodeFetcher(domain_runtime_code);
202 let runtime_code = RuntimeCode {
203 code_fetcher: &domain_runtime_code_fetcher,
204 hash: b"Hash of the code does not matter in terms of the execution proof check"
205 .to_vec(),
206 heap_pages: None,
207 };
208
209 let (domain_block_number, domain_block_hash) = domain_block_id;
210 let mut domain_extensions = (self.domain_extensions_factory_creator)(
211 self.consensus_client.clone(),
212 self.domain_executor.clone(),
213 )
214 .extensions_for(domain_block_hash.into(), domain_block_number.into());
215
216 execution_proof_check::<HashingFor<DomainBlock>, _>(
217 pre_state_root.into(),
218 proof,
219 &mut Default::default(),
220 self.domain_executor.as_ref(),
221 execution_method,
222 call_data,
223 &runtime_code,
224 &mut domain_extensions,
225 )
226 .ok()
227 }
228
229 fn check_extrinsics_in_single_context(
230 &self,
231 domain_runtime_code: Vec<u8>,
232 domain_block_id: (BlockNumber, H256),
233 domain_block_state_root: H256,
234 bundle_extrinsics: Vec<OpaqueExtrinsic>,
235 encoded_proof: Vec<u8>,
236 ) -> Option<Option<u32>> {
237 let storage_proof: StorageProof = Decode::decode(&mut encoded_proof.as_ref()).ok()?;
238 let (domain_block_number, domain_block_hash) = domain_block_id;
239
240 let raw_response = self.execution_proof_check(
241 domain_block_id,
242 domain_block_state_root,
243 storage_proof.encode(),
244 CHECK_EXTRINSICS_AND_DO_PRE_DISPATCH_METHOD_NAME,
245 &(&bundle_extrinsics, &domain_block_number, &domain_block_hash).encode(),
247 domain_runtime_code,
248 )?;
249
250 let bundle_extrinsics_validity_response: Result<(), CheckExtrinsicsValidityError> =
251 Decode::decode(&mut raw_response.as_slice()).ok()?;
252 if let Err(bundle_extrinsic_validity_error) = bundle_extrinsics_validity_response {
253 Some(Some(bundle_extrinsic_validity_error.extrinsic_index))
254 } else {
255 Some(None)
256 }
257 }
258
259 fn construct_domain_inherent_extrinsic(
260 &self,
261 domain_runtime_code: Vec<u8>,
262 domain_inherent_extrinsic_data: DomainInherentExtrinsicData,
263 ) -> Option<DomainInherentExtrinsic> {
264 let DomainInherentExtrinsicData {
265 timestamp,
266 maybe_domain_runtime_upgrade,
267 consensus_transaction_byte_fee,
268 domain_chain_allowlist,
269 maybe_sudo_runtime_call,
270 maybe_evm_domain_contract_creation_allowed_by_call,
271 } = domain_inherent_extrinsic_data;
272
273 let domain_stateless_runtime = StatelessRuntime::<Block, DomainBlock, _>::new(
274 self.domain_executor.clone(),
275 domain_runtime_code.into(),
276 );
277
278 let domain_timestamp_extrinsic = domain_stateless_runtime
279 .construct_timestamp_extrinsic(timestamp)
280 .ok()
281 .map(|ext| ext.encode())?;
282
283 let consensus_chain_byte_fee_extrinsic = domain_stateless_runtime
284 .construct_consensus_chain_byte_fee_extrinsic(consensus_transaction_byte_fee)
285 .ok()
286 .map(|ext| ext.encode())?;
287
288 let maybe_domain_chain_allowlist_extrinsic = if !domain_chain_allowlist.is_empty() {
289 Some(
290 domain_stateless_runtime
291 .construct_domain_update_chain_allowlist_extrinsic(domain_chain_allowlist)
292 .ok()
293 .map(|ext| ext.encode())?,
294 )
295 } else {
296 None
297 };
298
299 let maybe_domain_set_code_extrinsic = match maybe_domain_runtime_upgrade {
300 None => None,
301 Some(upgraded_runtime_code) => Some(
302 domain_stateless_runtime
303 .construct_set_code_extrinsic(upgraded_runtime_code)
304 .ok()
305 .map(|ext| ext.encode())?,
306 ),
307 };
308
309 let maybe_domain_sudo_call_extrinsic = match maybe_sudo_runtime_call {
310 None => None,
311 Some(call) => Some(
312 domain_stateless_runtime
313 .construct_domain_sudo_extrinsic(call)
314 .ok()
315 .map(|call| call.encode())?,
316 ),
317 };
318
319 let maybe_evm_domain_contract_creation_allowed_by_call_extrinsic =
320 match maybe_evm_domain_contract_creation_allowed_by_call {
321 None => None,
322 Some(call) => Some(
323 domain_stateless_runtime
324 .construct_evm_contract_creation_allowed_by_extrinsic(call)
325 .ok()
326 .map(|call| call.encode())?,
327 ),
328 };
329
330 Some(DomainInherentExtrinsic {
331 domain_timestamp_extrinsic,
332 maybe_domain_chain_allowlist_extrinsic,
333 consensus_chain_byte_fee_extrinsic,
334 maybe_domain_set_code_extrinsic,
335 maybe_domain_sudo_call_extrinsic,
336 maybe_evm_domain_contract_creation_allowed_by_call_extrinsic,
337 })
338 }
339
340 fn domain_storage_key(
341 &self,
342 domain_runtime_code: Vec<u8>,
343 req: DomainStorageKeyRequest,
344 ) -> Option<Vec<u8>> {
345 let domain_stateless_runtime = StatelessRuntime::<Block, DomainBlock, _>::new(
346 self.domain_executor.clone(),
347 domain_runtime_code.into(),
348 );
349 let key = match req {
350 DomainStorageKeyRequest::BlockFees => domain_stateless_runtime.block_fees_storage_key(),
351 DomainStorageKeyRequest::Transfers => domain_stateless_runtime.transfers_storage_key(),
352 }
353 .ok()?;
354 Some(key)
355 }
356
357 fn domain_runtime_call(
358 &self,
359 domain_runtime_code: Vec<u8>,
360 call: StatelessDomainRuntimeCall,
361 ) -> Option<bool> {
362 let domain_stateless_runtime = StatelessRuntime::<Block, DomainBlock, _>::new(
363 self.domain_executor.clone(),
364 domain_runtime_code.into(),
365 );
366
367 match call {
370 StatelessDomainRuntimeCall::IsTxInRange {
371 opaque_extrinsic,
372 bundle_vrf_hash,
373 domain_tx_range,
374 } => {
375 let encoded_extrinsic = opaque_extrinsic.encode();
376 let extrinsic =
377 DomainBlock::Extrinsic::decode(&mut encoded_extrinsic.as_slice()).ok()?;
378 domain_stateless_runtime
379 .is_within_tx_range(&extrinsic, &bundle_vrf_hash, &domain_tx_range)
380 .ok()
381 }
382 StatelessDomainRuntimeCall::IsInherentExtrinsic(opaque_extrinsic) => {
383 let encoded_extrinsic = opaque_extrinsic.encode();
384 let extrinsic =
385 DomainBlock::Extrinsic::decode(&mut encoded_extrinsic.as_slice()).ok()?;
386 domain_stateless_runtime
387 .is_inherent_extrinsic(&extrinsic)
388 .ok()
389 }
390 StatelessDomainRuntimeCall::IsDecodableExtrinsic(opaque_extrinsic) => Some(matches!(
391 domain_stateless_runtime.decode_extrinsic(opaque_extrinsic),
392 Ok(Ok(_))
393 )),
394 StatelessDomainRuntimeCall::IsValidDomainSudoCall(encoded_extrinsic) => {
395 domain_stateless_runtime
396 .is_valid_sudo_call(encoded_extrinsic)
397 .ok()
398 }
399 }
400 }
401
402 fn bundle_weight(
403 &self,
404 domain_runtime_code: Vec<u8>,
405 bundle_body: Vec<OpaqueExtrinsic>,
406 ) -> Option<Weight> {
407 let domain_stateless_runtime = StatelessRuntime::<Block, DomainBlock, _>::new(
408 self.domain_executor.clone(),
409 domain_runtime_code.into(),
410 );
411 let mut estimated_bundle_weight = Weight::default();
412 for opaque_extrinsic in bundle_body {
413 let encoded_extrinsic = opaque_extrinsic.encode();
414 let extrinsic =
415 DomainBlock::Extrinsic::decode(&mut encoded_extrinsic.as_slice()).ok()?;
416 let tx_weight = domain_stateless_runtime.extrinsic_weight(&extrinsic).ok()?;
417 estimated_bundle_weight = estimated_bundle_weight.saturating_add(tx_weight);
418 }
419 Some(estimated_bundle_weight)
420 }
421
422 fn extract_xdm_mmr_proof(
423 &self,
424 domain_runtime_code: Vec<u8>,
425 opaque_extrinsic: Vec<u8>,
426 ) -> Option<Option<Vec<u8>>> {
427 let domain_stateless_runtime = StatelessRuntime::<Block, DomainBlock, _>::new(
428 self.domain_executor.clone(),
429 domain_runtime_code.into(),
430 );
431 let extrinsic = DomainBlock::Extrinsic::decode(&mut opaque_extrinsic.as_slice()).ok()?;
432 domain_stateless_runtime
433 .extract_xdm_mmr_proof(&extrinsic)
434 .ok()
435 }
436}
437
438type CreateProofCheckBackedResult<H> = Result<
439 (TrieBackend<MemoryDB<H>, H>, sp_trie::recorder::Recorder<H>),
440 Box<dyn sp_state_machine::Error>,
441>;
442
443fn create_proof_check_backend_with_recorder<H>(
446 root: H::Out,
447 proof: StorageProof,
448) -> CreateProofCheckBackedResult<H>
449where
450 H: Hasher,
451 H::Out: Codec,
452{
453 let db = proof.into_memory_db();
454 let recorder = sp_trie::recorder::Recorder::<H>::default();
455
456 if db.contains(&root, hash_db::EMPTY_PREFIX) {
457 let backend = TrieBackendBuilder::new(db, root)
458 .with_recorder(recorder.clone())
459 .build();
460 Ok((backend, recorder))
461 } else {
462 Err(Box::new(sp_state_machine::ExecutionError::InvalidProof))
463 }
464}
465
466#[derive(Debug)]
468pub(crate) enum ExecutionProofCheckError {
469 #[allow(dead_code)]
472 ExecutionError(Box<dyn sp_state_machine::Error>),
473 UnusedNodes,
475}
476
477impl From<Box<dyn sp_state_machine::Error>> for ExecutionProofCheckError {
478 fn from(value: Box<dyn sp_state_machine::Error>) -> Self {
479 Self::ExecutionError(value)
480 }
481}
482
483#[allow(clippy::too_many_arguments)]
484pub(crate) fn execution_proof_check<H, Exec>(
487 root: H::Out,
488 proof: StorageProof,
489 overlay: &mut OverlayedChanges<H>,
490 exec: &Exec,
491 method: &str,
492 call_data: &[u8],
493 runtime_code: &RuntimeCode,
494 extensions: &mut Extensions,
495) -> Result<Vec<u8>, ExecutionProofCheckError>
496where
497 H: Hasher,
498 H::Out: Ord + 'static + Codec,
499 Exec: CodeExecutor + Clone + 'static,
500{
501 let expected_nodes_to_be_read = proof.iter_nodes().count();
502 let (trie_backend, recorder) = create_proof_check_backend_with_recorder::<H>(root, proof)?;
503 let result = StateMachine::<_, H, Exec>::new(
504 &trie_backend,
505 overlay,
506 exec,
507 method,
508 call_data,
509 extensions,
510 runtime_code,
511 CallContext::Offchain,
512 )
513 .execute()?;
514
515 let recorded_proof = recorder.drain_storage_proof();
516 if recorded_proof.iter_nodes().count() != expected_nodes_to_be_read {
517 return Err(ExecutionProofCheckError::UnusedNodes);
518 }
519
520 Ok(result)
521}