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 frame_support::pallet_prelude::Zero;
9use parity_scale_codec::{Decode, Encode};
10use scale_info::TypeInfo;
11use sp_core::H256;
12use sp_domain_digests::AsPredigest;
13use sp_domains::bundle::{BundleValidity, InvalidBundleType};
14use sp_domains::execution_receipt::ExecutionReceiptFor;
15use sp_domains::proof_provider_and_verifier::StorageProofVerifier;
16use sp_domains::{DomainId, ExtrinsicDigest, HeaderHashFor, HeaderHashingFor};
17use sp_runtime::traits::{Block as BlockT, Hash, 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 Balance: Encode + Zero + Default,
109 {
110 if bad_receipt.execution_traces().len() < 2 {
111 return Err(VerificationError::InvalidExecutionTrace);
112 }
113 let (pre, post) = match self {
114 ExecutionPhase::InitializeBlock => (
115 *bad_receipt_parent.final_state_root(),
116 bad_receipt.execution_traces()[0],
117 ),
118 ExecutionPhase::ApplyExtrinsic {
119 mismatch: ApplyExtrinsicMismatch::StateRoot(mismatch_index),
120 ..
121 } => {
122 if *mismatch_index == 0
123 || *mismatch_index >= bad_receipt.execution_traces().len() as u32 - 1
124 {
125 return Err(VerificationError::InvalidApplyExtrinsicTraceIndex);
126 }
127 (
128 bad_receipt.execution_traces()[*mismatch_index as usize - 1],
129 bad_receipt.execution_traces()[*mismatch_index as usize],
130 )
131 }
132 ExecutionPhase::ApplyExtrinsic {
133 mismatch: ApplyExtrinsicMismatch::Shorter,
134 ..
135 } => {
136 let mismatch_index = bad_receipt.execution_traces().len() - 1;
137 (
138 bad_receipt.execution_traces()[mismatch_index - 1],
139 bad_receipt.execution_traces()[mismatch_index],
140 )
141 }
142 ExecutionPhase::FinalizeBlock {
143 mismatch: FinalizeBlockMismatch::StateRoot,
144 } => {
145 let mismatch_index = bad_receipt.execution_traces().len() - 1;
146 (
147 bad_receipt.execution_traces()[mismatch_index - 1],
148 bad_receipt.execution_traces()[mismatch_index],
149 )
150 }
151 ExecutionPhase::FinalizeBlock {
152 mismatch: FinalizeBlockMismatch::Longer(mismatch_index),
153 } => {
154 if *mismatch_index == 0
155 || *mismatch_index >= bad_receipt.execution_traces().len() as u32 - 1
156 {
157 return Err(VerificationError::InvalidLongerMismatchTraceIndex);
158 }
159 (
160 bad_receipt.execution_traces()[(*mismatch_index - 1) as usize],
161 bad_receipt.execution_traces()[*mismatch_index as usize],
162 )
163 }
164 };
165 Ok((pre.into(), post.into()))
166 }
167
168 pub fn call_data<CBlock, DomainHeader, Balance>(
169 &self,
170 bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
171 bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
172 ) -> Result<Vec<u8>, VerificationError<DomainHeader::Hash>>
173 where
174 CBlock: BlockT,
175 DomainHeader: HeaderT,
176 Balance: Encode + Zero + Default,
177 {
178 Ok(match self {
179 ExecutionPhase::InitializeBlock => {
180 let inherent_digests = Digest {
181 logs: sp_std::vec![DigestItem::consensus_block_info(
182 bad_receipt.consensus_block_hash(),
183 )],
184 };
185
186 let new_header = DomainHeader::new(
187 *bad_receipt.domain_block_number(),
188 Default::default(),
189 Default::default(),
190 *bad_receipt_parent.domain_block_hash(),
191 inherent_digests,
192 );
193 new_header.encode()
194 }
195 ExecutionPhase::ApplyExtrinsic {
196 extrinsic_proof: proof_of_inclusion,
197 mismatch,
198 } => {
199 let mismatch_index = match mismatch {
200 ApplyExtrinsicMismatch::StateRoot(mismatch_index) => *mismatch_index,
201 ApplyExtrinsicMismatch::Shorter => {
202 (bad_receipt.execution_traces().len() - 1) as u32
203 }
204 };
205 let extrinsic_index: u32 = mismatch_index - 1;
208
209 let storage_key =
210 StorageProofVerifier::<DomainHeader::Hashing>::enumerated_storage_key(
211 extrinsic_index,
212 );
213
214 StorageProofVerifier::<DomainHeader::Hashing>::get_bare_value(
215 bad_receipt.domain_block_extrinsics_root(),
216 proof_of_inclusion.clone(),
217 storage_key,
218 )
219 .map_err(|_| VerificationError::InvalidApplyExtrinsicCallData)?
220 }
221 ExecutionPhase::FinalizeBlock { .. } => Vec::new(),
222 })
223 }
224}
225
226#[derive(Debug, thiserror::Error)]
228pub enum VerificationError<DomainHash> {
229 #[error("Failed to pass the execution proof check")]
231 BadExecutionProof,
232 #[error("The fraud proof prove nothing invalid")]
234 InvalidProof,
235 #[error("Failed to decode the return value of `initialize_block` and `apply_extrinsic`: {0}")]
237 InitializeBlockOrApplyExtrinsicDecode(parity_scale_codec::Error),
238 #[error(
240 "Failed to decode the storage root from verifying `initialize_block` and `apply_extrinsic`: {0}"
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_traces 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)]
320pub struct FraudProof<Number, Hash, DomainHeader: HeaderT, MmrHash> {
321 pub domain_id: DomainId,
322 pub bad_receipt_hash: HeaderHashFor<DomainHeader>,
324 pub maybe_mmr_proof: Option<ConsensusChainMmrLeafProof<Number, Hash, MmrHash>>,
328 pub maybe_domain_runtime_code_proof: Option<DomainRuntimeCodeAt<Number, Hash, MmrHash>>,
333 pub proof: FraudProofVariant<Number, Hash, MmrHash, DomainHeader>,
335}
336
337#[allow(clippy::large_enum_variant)]
338#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
339pub enum FraudProofVariant<Number, Hash, MmrHash, DomainHeader: HeaderT> {
340 #[codec(index = 0)]
341 InvalidStateTransition(InvalidStateTransitionProof),
342 #[codec(index = 1)]
343 ValidBundle(ValidBundleProof<Number, Hash, DomainHeader>),
344 #[codec(index = 2)]
345 InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof),
346 #[codec(index = 3)]
347 InvalidBundles(InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader>),
348 #[codec(index = 4)]
349 InvalidDomainBlockHash(InvalidDomainBlockHashProof),
350 #[codec(index = 5)]
351 InvalidBlockFees(InvalidBlockFeesProof),
352 #[codec(index = 6)]
353 InvalidTransfers(InvalidTransfersProof),
354 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
356 #[codec(index = 100)]
357 Dummy,
358}
359
360impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash> {
361 pub fn domain_id(&self) -> DomainId {
362 self.domain_id
363 }
364
365 pub fn targeted_bad_receipt_hash(&self) -> HeaderHashFor<DomainHeader> {
366 self.bad_receipt_hash
367 }
368
369 pub fn is_unexpected_domain_runtime_code_proof(&self) -> bool {
370 self.maybe_domain_runtime_code_proof.is_some()
374 && matches!(self.proof, FraudProofVariant::InvalidDomainBlockHash(_))
375 }
376
377 pub fn is_unexpected_mmr_proof(&self) -> bool {
378 if self.maybe_mmr_proof.is_none() {
379 return false;
380 }
381 !matches!(
385 self.proof,
386 FraudProofVariant::InvalidExtrinsicsRoot(_)
387 | FraudProofVariant::InvalidBundles(_)
388 | FraudProofVariant::ValidBundle(_)
389 )
390 }
391
392 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
393 pub fn dummy_fraud_proof(
394 domain_id: DomainId,
395 bad_receipt_hash: HeaderHashFor<DomainHeader>,
396 ) -> FraudProof<Number, Hash, DomainHeader, MmrHash> {
397 Self {
398 domain_id,
399 bad_receipt_hash,
400 maybe_mmr_proof: None,
401 maybe_domain_runtime_code_proof: None,
402 proof: FraudProofVariant::Dummy,
403 }
404 }
405}
406
407impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash>
408where
409 Number: Encode,
410 Hash: Encode,
411 MmrHash: Encode,
412{
413 pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
414 HeaderHashingFor::<DomainHeader>::hash(&self.encode())
415 }
416}
417
418impl<Number, Hash, MmrHash, DomainHeader: HeaderT> fmt::Debug
419 for FraudProof<Number, Hash, DomainHeader, MmrHash>
420{
421 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422 let fp_target =
423 scale_info::prelude::format!("{:?}#{:?}", self.domain_id, self.bad_receipt_hash);
424 match &self.proof {
425 FraudProofVariant::InvalidStateTransition(_) => {
426 write!(f, "InvalidStateTransitionFraudProof({fp_target})")
427 }
428 FraudProofVariant::InvalidExtrinsicsRoot(_) => {
429 write!(f, "InvalidExtrinsicsRootFraudProof({fp_target})")
430 }
431 FraudProofVariant::InvalidBlockFees(_) => {
432 write!(f, "InvalidBlockFeesFraudProof({fp_target})")
433 }
434 FraudProofVariant::ValidBundle(_) => {
435 write!(f, "ValidBundleFraudProof({fp_target})")
436 }
437 FraudProofVariant::InvalidBundles(proof) => {
438 write!(
439 f,
440 "InvalidBundlesFraudProof(type: {:?}, target: {fp_target})",
441 proof.invalid_bundle_type()
442 )
443 }
444 FraudProofVariant::InvalidDomainBlockHash(_) => {
445 write!(f, "InvalidDomainBlockHashFraudProof({fp_target})")
446 }
447 FraudProofVariant::InvalidTransfers(_) => {
448 write!(f, "InvalidTransfersFraudProof({fp_target})")
449 }
450 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
451 FraudProofVariant::Dummy => {
452 write!(f, "DummyFraudProof({fp_target})")
453 }
454 }
455 }
456}
457
458#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
460pub struct ValidBundleDigest {
461 pub bundle_index: u32,
463 pub bundle_digest: Vec<(
465 Option<domain_runtime_primitives::opaque::AccountId>,
466 ExtrinsicDigest,
467 )>,
468}
469
470#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
472pub struct DomainRuntimeCodeAt<Number, Hash, MmrHash> {
473 pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
474 pub domain_runtime_code_proof: DomainRuntimeCodeProof,
475}
476
477#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
479pub struct InvalidStateTransitionProof {
480 pub execution_proof: StorageProof,
482 pub execution_phase: ExecutionPhase,
484}
485
486#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
488pub struct ValidBundleProof<Number, Hash, DomainHeader: HeaderT> {
489 pub bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
491}
492
493impl<Number, Hash, DomainHeader: HeaderT> ValidBundleProof<Number, Hash, DomainHeader> {
494 pub fn set_bundle_index(&mut self, index: u32) {
495 self.bundle_with_proof.bundle_index = index
496 }
497}
498
499#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
500pub struct InvalidExtrinsicsRootProof {
501 pub valid_bundle_digests: Vec<ValidBundleDigest>,
503
504 pub invalid_inherent_extrinsic_proofs: InvalidInherentExtrinsicDataProof,
506
507 pub maybe_domain_runtime_upgraded_proof: MaybeDomainRuntimeUpgradedProof,
509
510 pub domain_chain_allowlist_proof: DomainChainsAllowlistUpdateStorageProof,
512
513 pub domain_sudo_call_proof: DomainSudoCallStorageProof,
515
516 pub evm_domain_contract_creation_allowed_by_call_proof:
518 EvmDomainContractCreationAllowedByCallStorageProof,
519}
520
521#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
522pub struct MmrRootProof<Number, Hash, MmrHash> {
523 pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
524 pub mmr_root_storage_proof: MmrRootStorageProof<MmrHash>,
525}
526
527#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
529pub enum InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader: HeaderT> {
530 Extrinsic(StorageProof),
531 Bundle(OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>),
532 BundleAndExecution {
533 bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
534 execution_proof: StorageProof,
535 },
536 InvalidXDMProofData {
537 extrinsic_proof: StorageProof,
538 mmr_root_proof: Option<MmrRootProof<Number, Hash, MmrHash>>,
539 },
540}
541
542#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
544pub struct InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader: HeaderT> {
545 pub bundle_index: u32,
546 pub invalid_bundle_type: InvalidBundleType,
548 pub is_good_invalid_fraud_proof: bool,
551 pub proof_data: InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader>,
553}
554
555impl<Number, Hash, MmrHash, DomainHeader: HeaderT>
556 InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader>
557{
558 pub fn invalid_bundle_type(&self) -> InvalidBundleType {
559 self.invalid_bundle_type.clone()
560 }
561
562 pub fn is_good_invalid_fraud_proof(&self) -> bool {
563 self.is_good_invalid_fraud_proof
564 }
565}
566
567#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
569pub struct InvalidBlockFeesProof {
570 pub storage_proof: StorageProof,
572}
573
574#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
576pub struct InvalidTransfersProof {
577 pub storage_proof: StorageProof,
579}
580
581#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
583pub struct InvalidDomainBlockHashProof {
584 pub digest_storage_proof: StorageProof,
586}