sp_domains_fraud_proof/
host_functions.rs

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
44/// Trait to query and verify Domains Fraud proof.
45pub trait FraudProofHostFunctions: Send + Sync {
46    /// Derive the bundle digest for the given bundle body.
47    fn derive_bundle_digest(
48        &self,
49        domain_runtime_code: Vec<u8>,
50        bundle_body: Vec<OpaqueExtrinsic>,
51    ) -> Option<H256>;
52
53    /// Check the execution proof
54    fn execution_proof_check(
55        &self,
56        domain_block_id: (BlockNumber, H256),
57        pre_state_root: H256,
58        // TODO: implement `PassBy` for `sp_trie::StorageProof` in upstream to pass it directly here
59        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        // TODO: implement `PassBy` for `sp_trie::StorageProof` in upstream to pass it directly here
72        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    /// Domains fraud proof host function
108    pub struct FraudProofExtension(std::sync::Arc<dyn FraudProofHostFunctions>);
109}
110
111impl FraudProofExtension {
112    /// Create a new instance of [`FraudProofExtension`].
113    pub fn new(inner: std::sync::Arc<dyn FraudProofHostFunctions>) -> Self {
114        Self(inner)
115    }
116}
117
118/// Trait Impl to query and verify Domains Fraud proof.
119pub 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            // The call data must be encoded form of arguments to `DomainCoreApi::check_extrinsic_and_do_pre_dispatch`
246            &(&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        // These decodes change the opaque extrinsic type to another opaque type, so they don't
368        // need depth limits. (Only runtime code can correctly decode runtime calls.)
369        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
443/// Creates a memory db backed with a recorder.
444/// Fork of `sp_state_machine::create_proof_check_backend` with recorder enabled.
445fn 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/// Execution Proof check error.
467#[derive(Debug)]
468pub(crate) enum ExecutionProofCheckError {
469    /// Holds the actual execution proof error
470    // While we don't read the error, we'd still like it to be present in case we need it later
471    #[allow(dead_code)]
472    ExecutionError(Box<dyn sp_state_machine::Error>),
473    /// Error when storage proof contains unused node keys.
474    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)]
484/// Executes the given proof using the runtime
485/// The only difference between sp_state_machine::execution_proof_check is Extensions
486pub(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}