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 #[error(
238 "Failed to decode the storage root from verifying `initialize_block` and `apply_extrinsic`: {0}"
239 )]
240 StorageRootDecode(parity_scale_codec::Error),
241 #[error("Failed to decode the header from verifying `finalize_block`: {0}")]
243 HeaderDecode(parity_scale_codec::Error),
244 #[error("The receipt's execution_trace have less than 2 traces")]
245 InvalidExecutionTrace,
246 #[error("Invalid ApplyExtrinsic trace index")]
247 InvalidApplyExtrinsicTraceIndex,
248 #[error("Invalid longer mismatch trace index")]
249 InvalidLongerMismatchTraceIndex,
250 #[error("Invalid ApplyExtrinsic call data")]
251 InvalidApplyExtrinsicCallData,
252 #[error("Invalid Bundle Digest")]
254 InvalidBundleDigest,
255 #[error("Bundle with requested index not found in execution receipt")]
257 BundleNotFound,
258 #[error(
260 "Unexpected bundle entry at {bundle_index} in bad receipt found: \
261 {targeted_entry_bundle:?} with fraud proof's type of proof: \
262 {fraud_proof_invalid_type_of_proof:?}"
263 )]
264 UnexpectedTargetedBundleEntry {
265 bundle_index: u32,
266 fraud_proof_invalid_type_of_proof: InvalidBundleType,
267 targeted_entry_bundle: BundleValidity<DomainHash>,
268 },
269 #[error("Failed to derive bundle digest")]
271 FailedToDeriveBundleDigest,
272 #[error("The target valid bundle not found from the target bad receipt")]
274 TargetValidBundleNotFound,
275 #[error("Failed to check extrinsics in single context")]
277 FailedToCheckExtrinsicsInSingleContext,
278 #[error(
279 "Bad MMR proof, the proof is probably expired or is generated against a different fork"
280 )]
281 BadMmrProof,
282 #[error("Unexpected MMR proof")]
283 UnexpectedMmrProof,
284 #[error("Failed to verify storage proof")]
285 StorageProof(storage_proof::VerificationError),
286 #[error("Failed to derive domain inherent extrinsic")]
288 FailedToDeriveDomainInherentExtrinsic,
289 #[error("Failed to derive domain storage key")]
291 FailedToGetDomainStorageKey,
292 #[error("Unexpected invalid bundle proof data")]
294 UnexpectedInvalidBundleProofData,
295 #[error("Extrinsic with requested index not found in bundle")]
297 ExtrinsicNotFound,
298 #[error("Failed to get domain runtime call response")]
300 FailedToGetDomainRuntimeCallResponse,
301 #[error("Failed to get bundle weight")]
303 FailedToGetBundleWeight,
304 #[error("Failed to extract xdm mmr proof")]
305 FailedToGetExtractXdmMmrProof,
306 #[error("Failed to decode xdm mmr proof")]
307 FailedToDecodeXdmMmrProof,
308}
309
310impl<DomainHash> From<storage_proof::VerificationError> for VerificationError<DomainHash> {
311 fn from(err: storage_proof::VerificationError) -> Self {
312 Self::StorageProof(err)
313 }
314}
315
316#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
317pub struct FraudProof<Number, Hash, DomainHeader: HeaderT, MmrHash> {
318 pub domain_id: DomainId,
319 pub bad_receipt_hash: HeaderHashFor<DomainHeader>,
321 pub maybe_mmr_proof: Option<ConsensusChainMmrLeafProof<Number, Hash, MmrHash>>,
325 pub maybe_domain_runtime_code_proof: Option<DomainRuntimeCodeAt<Number, Hash, MmrHash>>,
330 pub proof: FraudProofVariant<Number, Hash, MmrHash, DomainHeader>,
332}
333
334#[allow(clippy::large_enum_variant)]
335#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
336pub enum FraudProofVariant<Number, Hash, MmrHash, DomainHeader: HeaderT> {
337 #[codec(index = 0)]
338 InvalidStateTransition(InvalidStateTransitionProof),
339 #[codec(index = 1)]
340 ValidBundle(ValidBundleProof<Number, Hash, DomainHeader>),
341 #[codec(index = 2)]
342 InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof),
343 #[codec(index = 3)]
344 InvalidBundles(InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader>),
345 #[codec(index = 4)]
346 InvalidDomainBlockHash(InvalidDomainBlockHashProof),
347 #[codec(index = 5)]
348 InvalidBlockFees(InvalidBlockFeesProof),
349 #[codec(index = 6)]
350 InvalidTransfers(InvalidTransfersProof),
351 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
353 #[codec(index = 100)]
354 Dummy,
355}
356
357impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash> {
358 pub fn domain_id(&self) -> DomainId {
359 self.domain_id
360 }
361
362 pub fn targeted_bad_receipt_hash(&self) -> HeaderHashFor<DomainHeader> {
363 self.bad_receipt_hash
364 }
365
366 pub fn is_unexpected_domain_runtime_code_proof(&self) -> bool {
367 self.maybe_domain_runtime_code_proof.is_some()
371 && matches!(self.proof, FraudProofVariant::InvalidDomainBlockHash(_))
372 }
373
374 pub fn is_unexpected_mmr_proof(&self) -> bool {
375 if self.maybe_mmr_proof.is_none() {
376 return false;
377 }
378 !matches!(
382 self.proof,
383 FraudProofVariant::InvalidExtrinsicsRoot(_)
384 | FraudProofVariant::InvalidBundles(_)
385 | FraudProofVariant::ValidBundle(_)
386 )
387 }
388
389 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
390 pub fn dummy_fraud_proof(
391 domain_id: DomainId,
392 bad_receipt_hash: HeaderHashFor<DomainHeader>,
393 ) -> FraudProof<Number, Hash, DomainHeader, MmrHash> {
394 Self {
395 domain_id,
396 bad_receipt_hash,
397 maybe_mmr_proof: None,
398 maybe_domain_runtime_code_proof: None,
399 proof: FraudProofVariant::Dummy,
400 }
401 }
402}
403
404impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash>
405where
406 Number: Encode,
407 Hash: Encode,
408 MmrHash: Encode,
409{
410 pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
411 HeaderHashingFor::<DomainHeader>::hash(&self.encode())
412 }
413}
414
415impl<Number, Hash, MmrHash, DomainHeader: HeaderT> fmt::Debug
416 for FraudProof<Number, Hash, DomainHeader, MmrHash>
417{
418 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 let fp_target =
420 scale_info::prelude::format!("{:?}#{:?}", self.domain_id, self.bad_receipt_hash);
421 match &self.proof {
422 FraudProofVariant::InvalidStateTransition(_) => {
423 write!(f, "InvalidStateTransitionFraudProof({fp_target})")
424 }
425 FraudProofVariant::InvalidExtrinsicsRoot(_) => {
426 write!(f, "InvalidExtrinsicsRootFraudProof({fp_target})")
427 }
428 FraudProofVariant::InvalidBlockFees(_) => {
429 write!(f, "InvalidBlockFeesFraudProof({fp_target})")
430 }
431 FraudProofVariant::ValidBundle(_) => {
432 write!(f, "ValidBundleFraudProof({fp_target})")
433 }
434 FraudProofVariant::InvalidBundles(proof) => {
435 write!(
436 f,
437 "InvalidBundlesFraudProof(type: {:?}, target: {fp_target})",
438 proof.invalid_bundle_type
439 )
440 }
441 FraudProofVariant::InvalidDomainBlockHash(_) => {
442 write!(f, "InvalidDomainBlockHashFraudProof({fp_target})")
443 }
444 FraudProofVariant::InvalidTransfers(_) => {
445 write!(f, "InvalidTransfersFraudProof({fp_target})")
446 }
447 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
448 FraudProofVariant::Dummy => {
449 write!(f, "DummyFraudProof({fp_target})")
450 }
451 }
452 }
453}
454
455#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
457pub struct ValidBundleDigest {
458 pub bundle_index: u32,
460 pub bundle_digest: Vec<(
462 Option<domain_runtime_primitives::opaque::AccountId>,
463 ExtrinsicDigest,
464 )>,
465}
466
467#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
469pub struct DomainRuntimeCodeAt<Number, Hash, MmrHash> {
470 pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
471 pub domain_runtime_code_proof: DomainRuntimeCodeProof,
472}
473
474#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
476pub struct InvalidStateTransitionProof {
477 pub execution_proof: StorageProof,
479 pub execution_phase: ExecutionPhase,
481}
482
483#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
485pub struct ValidBundleProof<Number, Hash, DomainHeader: HeaderT> {
486 pub bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
488}
489
490#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
491pub struct InvalidExtrinsicsRootProof {
492 pub valid_bundle_digests: Vec<ValidBundleDigest>,
494
495 pub invalid_inherent_extrinsic_proofs: InvalidInherentExtrinsicDataProof,
497
498 pub maybe_domain_runtime_upgraded_proof: MaybeDomainRuntimeUpgradedProof,
500
501 pub domain_chain_allowlist_proof: DomainChainsAllowlistUpdateStorageProof,
503
504 pub domain_sudo_call_proof: DomainSudoCallStorageProof,
506
507 pub evm_domain_contract_creation_allowed_by_call_proof:
509 EvmDomainContractCreationAllowedByCallStorageProof,
510}
511
512#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
513pub struct MmrRootProof<Number, Hash, MmrHash> {
514 pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
515 pub mmr_root_storage_proof: MmrRootStorageProof<MmrHash>,
516}
517
518#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
519pub enum InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader: HeaderT> {
520 Extrinsic(StorageProof),
521 Bundle(OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>),
522 BundleAndExecution {
523 bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
524 execution_proof: StorageProof,
525 },
526 InvalidXDMProofData {
527 extrinsic_proof: StorageProof,
528 mmr_root_proof: Option<MmrRootProof<Number, Hash, MmrHash>>,
529 },
530}
531
532#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
534pub struct InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader: HeaderT> {
535 pub bundle_index: u32,
536 pub invalid_bundle_type: InvalidBundleType,
538 pub is_good_invalid_fraud_proof: bool,
541 pub proof_data: InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader>,
543}
544
545#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
547pub struct InvalidBlockFeesProof {
548 pub storage_proof: StorageProof,
550}
551
552#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
554pub struct InvalidTransfersProof {
555 pub storage_proof: StorageProof,
557}
558
559#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
561pub struct InvalidDomainBlockHashProof {
562 pub digest_storage_proof: StorageProof,
564}