1#[cfg(not(feature = "std"))]
2extern crate alloc;
3
4use crate::storage_proof::{self, *};
5#[cfg(not(feature = "std"))]
6use alloc::vec::Vec;
7use core::fmt;
8use parity_scale_codec::{Decode, Encode};
9use scale_info::TypeInfo;
10use sp_core::H256;
11use sp_domain_digests::AsPredigest;
12use sp_domains::proof_provider_and_verifier::StorageProofVerifier;
13use sp_domains::{
14 BundleValidity, DomainId, ExecutionReceiptFor, ExtrinsicDigest, HeaderHashFor,
15 HeaderHashingFor, InvalidBundleType,
16};
17use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT};
18use sp_runtime::{Digest, DigestItem};
19use sp_subspace_mmr::ConsensusChainMmrLeafProof;
20use sp_trie::StorageProof;
21use subspace_runtime_primitives::Balance;
22
23#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
25pub enum ApplyExtrinsicMismatch {
26 StateRoot(u32),
27 Shorter,
28}
29
30#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
32pub enum FinalizeBlockMismatch {
33 StateRoot,
34 Longer(u32),
35}
36
37#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
40pub enum ExecutionPhase {
41 InitializeBlock,
43 ApplyExtrinsic {
45 extrinsic_proof: StorageProof,
46 mismatch: ApplyExtrinsicMismatch,
47 },
48 FinalizeBlock { mismatch: FinalizeBlockMismatch },
50}
51
52impl ExecutionPhase {
53 pub fn execution_method(&self) -> &'static str {
55 match self {
56 Self::InitializeBlock => "DomainCoreApi_initialize_block_with_post_state_root",
59 Self::ApplyExtrinsic { .. } => "DomainCoreApi_apply_extrinsic_with_post_state_root",
60 Self::FinalizeBlock { .. } => "BlockBuilder_finalize_block",
61 }
62 }
63
64 pub fn is_state_root_mismatch(&self) -> bool {
67 matches!(
68 self,
69 ExecutionPhase::InitializeBlock
70 | ExecutionPhase::ApplyExtrinsic {
71 mismatch: ApplyExtrinsicMismatch::StateRoot(_),
72 extrinsic_proof: _,
73 }
74 | ExecutionPhase::FinalizeBlock {
75 mismatch: FinalizeBlockMismatch::StateRoot,
76 }
77 )
78 }
79 pub fn decode_execution_result<Header: HeaderT>(
81 &self,
82 execution_result: Vec<u8>,
83 ) -> Result<Header::Hash, VerificationError<Header::Hash>> {
84 match self {
85 Self::InitializeBlock | Self::ApplyExtrinsic { .. } => {
86 let encoded_storage_root = Vec::<u8>::decode(&mut execution_result.as_slice())
87 .map_err(VerificationError::InitializeBlockOrApplyExtrinsicDecode)?;
88 Header::Hash::decode(&mut encoded_storage_root.as_slice())
89 .map_err(VerificationError::StorageRootDecode)
90 }
91 Self::FinalizeBlock { .. } => {
92 let new_header = Header::decode(&mut execution_result.as_slice())
93 .map_err(VerificationError::HeaderDecode)?;
94 Ok(*new_header.state_root())
95 }
96 }
97 }
98
99 pub fn pre_post_state_root<CBlock, DomainHeader, Balance>(
100 &self,
101 bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
102 bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
103 ) -> Result<(H256, H256), VerificationError<DomainHeader::Hash>>
104 where
105 CBlock: BlockT,
106 DomainHeader: HeaderT,
107 DomainHeader::Hash: Into<H256>,
108 {
109 if bad_receipt.execution_trace.len() < 2 {
110 return Err(VerificationError::InvalidExecutionTrace);
111 }
112 let (pre, post) = match self {
113 ExecutionPhase::InitializeBlock => (
114 bad_receipt_parent.final_state_root,
115 bad_receipt.execution_trace[0],
116 ),
117 ExecutionPhase::ApplyExtrinsic {
118 mismatch: ApplyExtrinsicMismatch::StateRoot(mismatch_index),
119 ..
120 } => {
121 if *mismatch_index == 0
122 || *mismatch_index >= bad_receipt.execution_trace.len() as u32 - 1
123 {
124 return Err(VerificationError::InvalidApplyExtrinsicTraceIndex);
125 }
126 (
127 bad_receipt.execution_trace[*mismatch_index as usize - 1],
128 bad_receipt.execution_trace[*mismatch_index as usize],
129 )
130 }
131 ExecutionPhase::ApplyExtrinsic {
132 mismatch: ApplyExtrinsicMismatch::Shorter,
133 ..
134 } => {
135 let mismatch_index = bad_receipt.execution_trace.len() - 1;
136 (
137 bad_receipt.execution_trace[mismatch_index - 1],
138 bad_receipt.execution_trace[mismatch_index],
139 )
140 }
141 ExecutionPhase::FinalizeBlock {
142 mismatch: FinalizeBlockMismatch::StateRoot,
143 } => {
144 let mismatch_index = bad_receipt.execution_trace.len() - 1;
145 (
146 bad_receipt.execution_trace[mismatch_index - 1],
147 bad_receipt.execution_trace[mismatch_index],
148 )
149 }
150 ExecutionPhase::FinalizeBlock {
151 mismatch: FinalizeBlockMismatch::Longer(mismatch_index),
152 } => {
153 if *mismatch_index == 0
154 || *mismatch_index >= bad_receipt.execution_trace.len() as u32 - 1
155 {
156 return Err(VerificationError::InvalidLongerMismatchTraceIndex);
157 }
158 (
159 bad_receipt.execution_trace[(*mismatch_index - 1) as usize],
160 bad_receipt.execution_trace[*mismatch_index as usize],
161 )
162 }
163 };
164 Ok((pre.into(), post.into()))
165 }
166
167 pub fn call_data<CBlock, DomainHeader, Balance>(
168 &self,
169 bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
170 bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
171 ) -> Result<Vec<u8>, VerificationError<DomainHeader::Hash>>
172 where
173 CBlock: BlockT,
174 DomainHeader: HeaderT,
175 {
176 Ok(match self {
177 ExecutionPhase::InitializeBlock => {
178 let inherent_digests = Digest {
179 logs: sp_std::vec![DigestItem::consensus_block_info(
180 bad_receipt.consensus_block_hash,
181 )],
182 };
183
184 let new_header = DomainHeader::new(
185 bad_receipt.domain_block_number,
186 Default::default(),
187 Default::default(),
188 bad_receipt_parent.domain_block_hash,
189 inherent_digests,
190 );
191 new_header.encode()
192 }
193 ExecutionPhase::ApplyExtrinsic {
194 extrinsic_proof: proof_of_inclusion,
195 mismatch,
196 } => {
197 let mismatch_index = match mismatch {
198 ApplyExtrinsicMismatch::StateRoot(mismatch_index) => *mismatch_index,
199 ApplyExtrinsicMismatch::Shorter => {
200 (bad_receipt.execution_trace.len() - 1) as u32
201 }
202 };
203 let extrinsic_index: u32 = mismatch_index - 1;
206
207 let storage_key =
208 StorageProofVerifier::<DomainHeader::Hashing>::enumerated_storage_key(
209 extrinsic_index,
210 );
211
212 StorageProofVerifier::<DomainHeader::Hashing>::get_bare_value(
213 &bad_receipt.domain_block_extrinsic_root,
214 proof_of_inclusion.clone(),
215 storage_key,
216 )
217 .map_err(|_| VerificationError::InvalidApplyExtrinsicCallData)?
218 }
219 ExecutionPhase::FinalizeBlock { .. } => Vec::new(),
220 })
221 }
222}
223
224#[derive(Debug, thiserror::Error)]
226pub enum VerificationError<DomainHash> {
227 #[error("Failed to pass the execution proof check")]
229 BadExecutionProof,
230 #[error("The fraud proof prove nothing invalid")]
232 InvalidProof,
233 #[error("Failed to decode the return value of `initialize_block` and `apply_extrinsic`: {0}")]
235 InitializeBlockOrApplyExtrinsicDecode(parity_scale_codec::Error),
236 #[
238 error(
239 "Failed to decode the storage root from verifying `initialize_block` and `apply_extrinsic`: {0}"
240 )
241 ]
242 StorageRootDecode(parity_scale_codec::Error),
243 #[error("Failed to decode the header from verifying `finalize_block`: {0}")]
245 HeaderDecode(parity_scale_codec::Error),
246 #[error("The receipt's execution_trace have less than 2 traces")]
247 InvalidExecutionTrace,
248 #[error("Invalid ApplyExtrinsic trace index")]
249 InvalidApplyExtrinsicTraceIndex,
250 #[error("Invalid longer mismatch trace index")]
251 InvalidLongerMismatchTraceIndex,
252 #[error("Invalid ApplyExtrinsic call data")]
253 InvalidApplyExtrinsicCallData,
254 #[error("Invalid Bundle Digest")]
256 InvalidBundleDigest,
257 #[error("Bundle with requested index not found in execution receipt")]
259 BundleNotFound,
260 #[error(
262 "Unexpected bundle entry at {bundle_index} in bad receipt found: \
263 {targeted_entry_bundle:?} with fraud proof's type of proof: \
264 {fraud_proof_invalid_type_of_proof:?}"
265 )]
266 UnexpectedTargetedBundleEntry {
267 bundle_index: u32,
268 fraud_proof_invalid_type_of_proof: InvalidBundleType,
269 targeted_entry_bundle: BundleValidity<DomainHash>,
270 },
271 #[error("Failed to derive bundle digest")]
273 FailedToDeriveBundleDigest,
274 #[error("The target valid bundle not found from the target bad receipt")]
276 TargetValidBundleNotFound,
277 #[error("Failed to check extrinsics in single context")]
279 FailedToCheckExtrinsicsInSingleContext,
280 #[error(
281 "Bad MMR proof, the proof is probably expired or is generated against a different fork"
282 )]
283 BadMmrProof,
284 #[error("Unexpected MMR proof")]
285 UnexpectedMmrProof,
286 #[error("Failed to verify storage proof")]
287 StorageProof(storage_proof::VerificationError),
288 #[error("Failed to derive domain inherent extrinsic")]
290 FailedToDeriveDomainInherentExtrinsic,
291 #[error("Failed to derive domain storage key")]
293 FailedToGetDomainStorageKey,
294 #[error("Unexpected invalid bundle proof data")]
296 UnexpectedInvalidBundleProofData,
297 #[error("Extrinsic with requested index not found in bundle")]
299 ExtrinsicNotFound,
300 #[error("Failed to get domain runtime call response")]
302 FailedToGetDomainRuntimeCallResponse,
303 #[error("Failed to get bundle weight")]
305 FailedToGetBundleWeight,
306 #[error("Failed to extract xdm mmr proof")]
307 FailedToGetExtractXdmMmrProof,
308 #[error("Failed to decode xdm mmr proof")]
309 FailedToDecodeXdmMmrProof,
310}
311
312impl<DomainHash> From<storage_proof::VerificationError> for VerificationError<DomainHash> {
313 fn from(err: storage_proof::VerificationError) -> Self {
314 Self::StorageProof(err)
315 }
316}
317
318#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
319pub struct FraudProof<Number, Hash, DomainHeader: HeaderT, MmrHash> {
320 pub domain_id: DomainId,
321 pub bad_receipt_hash: HeaderHashFor<DomainHeader>,
323 pub maybe_mmr_proof: Option<ConsensusChainMmrLeafProof<Number, Hash, MmrHash>>,
327 pub maybe_domain_runtime_code_proof: Option<DomainRuntimeCodeAt<Number, Hash, MmrHash>>,
332 pub proof: FraudProofVariant<Number, Hash, MmrHash, DomainHeader>,
334}
335
336#[allow(clippy::large_enum_variant)]
337#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
338pub enum FraudProofVariant<Number, Hash, MmrHash, DomainHeader: HeaderT> {
339 #[codec(index = 0)]
340 InvalidStateTransition(InvalidStateTransitionProof),
341 #[codec(index = 1)]
342 ValidBundle(ValidBundleProof<Number, Hash, DomainHeader>),
343 #[codec(index = 2)]
344 InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof),
345 #[codec(index = 3)]
346 InvalidBundles(InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader>),
347 #[codec(index = 4)]
348 InvalidDomainBlockHash(InvalidDomainBlockHashProof),
349 #[codec(index = 5)]
350 InvalidBlockFees(InvalidBlockFeesProof),
351 #[codec(index = 6)]
352 InvalidTransfers(InvalidTransfersProof),
353 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
355 #[codec(index = 100)]
356 Dummy,
357}
358
359impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash> {
360 pub fn domain_id(&self) -> DomainId {
361 self.domain_id
362 }
363
364 pub fn targeted_bad_receipt_hash(&self) -> HeaderHashFor<DomainHeader> {
365 self.bad_receipt_hash
366 }
367
368 pub fn is_unexpected_domain_runtime_code_proof(&self) -> bool {
369 self.maybe_domain_runtime_code_proof.is_some()
373 && matches!(self.proof, FraudProofVariant::InvalidDomainBlockHash(_))
374 }
375
376 pub fn is_unexpected_mmr_proof(&self) -> bool {
377 if self.maybe_mmr_proof.is_none() {
378 return false;
379 }
380 !matches!(
384 self.proof,
385 FraudProofVariant::InvalidExtrinsicsRoot(_)
386 | FraudProofVariant::InvalidBundles(_)
387 | FraudProofVariant::ValidBundle(_)
388 )
389 }
390
391 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
392 pub fn dummy_fraud_proof(
393 domain_id: DomainId,
394 bad_receipt_hash: HeaderHashFor<DomainHeader>,
395 ) -> FraudProof<Number, Hash, DomainHeader, MmrHash> {
396 Self {
397 domain_id,
398 bad_receipt_hash,
399 maybe_mmr_proof: None,
400 maybe_domain_runtime_code_proof: None,
401 proof: FraudProofVariant::Dummy,
402 }
403 }
404}
405
406impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash>
407where
408 Number: Encode,
409 Hash: Encode,
410 MmrHash: Encode,
411{
412 pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
413 HeaderHashingFor::<DomainHeader>::hash(&self.encode())
414 }
415}
416
417impl<Number, Hash, MmrHash, DomainHeader: HeaderT> fmt::Debug
418 for FraudProof<Number, Hash, DomainHeader, MmrHash>
419{
420 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
421 let fp_target =
422 scale_info::prelude::format!("{:?}#{:?}", self.domain_id, self.bad_receipt_hash);
423 match &self.proof {
424 FraudProofVariant::InvalidStateTransition(_) => {
425 write!(f, "InvalidStateTransitionFraudProof({fp_target})")
426 }
427 FraudProofVariant::InvalidExtrinsicsRoot(_) => {
428 write!(f, "InvalidExtrinsicsRootFraudProof({fp_target})")
429 }
430 FraudProofVariant::InvalidBlockFees(_) => {
431 write!(f, "InvalidBlockFeesFraudProof({fp_target})")
432 }
433 FraudProofVariant::ValidBundle(_) => {
434 write!(f, "ValidBundleFraudProof({fp_target})")
435 }
436 FraudProofVariant::InvalidBundles(proof) => {
437 write!(
438 f,
439 "InvalidBundlesFraudProof(type: {:?}, target: {fp_target})",
440 proof.invalid_bundle_type
441 )
442 }
443 FraudProofVariant::InvalidDomainBlockHash(_) => {
444 write!(f, "InvalidDomainBlockHashFraudProof({fp_target})")
445 }
446 FraudProofVariant::InvalidTransfers(_) => {
447 write!(f, "InvalidTransfersFraudProof({fp_target})")
448 }
449 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
450 FraudProofVariant::Dummy => {
451 write!(f, "DummyFraudProof({fp_target})")
452 }
453 }
454 }
455}
456
457#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
459pub struct ValidBundleDigest {
460 pub bundle_index: u32,
462 pub bundle_digest: Vec<(
464 Option<domain_runtime_primitives::opaque::AccountId>,
465 ExtrinsicDigest,
466 )>,
467}
468
469#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
471pub struct DomainRuntimeCodeAt<Number, Hash, MmrHash> {
472 pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
473 pub domain_runtime_code_proof: DomainRuntimeCodeProof,
474}
475
476#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
478pub struct InvalidStateTransitionProof {
479 pub execution_proof: StorageProof,
481 pub execution_phase: ExecutionPhase,
483}
484
485#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
487pub struct ValidBundleProof<Number, Hash, DomainHeader: HeaderT> {
488 pub bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
490}
491
492#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
493pub struct InvalidExtrinsicsRootProof {
494 pub valid_bundle_digests: Vec<ValidBundleDigest>,
496
497 pub invalid_inherent_extrinsic_proofs: InvalidInherentExtrinsicDataProof,
499
500 pub maybe_domain_runtime_upgraded_proof: MaybeDomainRuntimeUpgradedProof,
502
503 pub domain_chain_allowlist_proof: DomainChainsAllowlistUpdateStorageProof,
505
506 pub domain_sudo_call_proof: DomainSudoCallStorageProof,
508
509 pub evm_domain_contract_creation_allowed_by_call_proof:
511 EvmDomainContractCreationAllowedByCallStorageProof,
512}
513
514#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
515pub struct MmrRootProof<Number, Hash, MmrHash> {
516 pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
517 pub mmr_root_storage_proof: MmrRootStorageProof<MmrHash>,
518}
519
520#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
521pub enum InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader: HeaderT> {
522 Extrinsic(StorageProof),
523 Bundle(OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>),
524 BundleAndExecution {
525 bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
526 execution_proof: StorageProof,
527 },
528 InvalidXDMProofData {
529 extrinsic_proof: StorageProof,
530 mmr_root_proof: Option<MmrRootProof<Number, Hash, MmrHash>>,
531 },
532}
533
534#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
536pub struct InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader: HeaderT> {
537 pub bundle_index: u32,
538 pub invalid_bundle_type: InvalidBundleType,
540 pub is_good_invalid_fraud_proof: bool,
543 pub proof_data: InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader>,
545}
546
547#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
549pub struct InvalidBlockFeesProof {
550 pub storage_proof: StorageProof,
552}
553
554#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
556pub struct InvalidTransfersProof {
557 pub storage_proof: StorageProof,
559}
560
561#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
563pub struct InvalidDomainBlockHashProof {
564 pub digest_storage_proof: StorageProof,
566}