#[cfg(not(feature = "std"))]
extern crate alloc;
use crate::storage_proof::{self, *};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use codec::{Decode, Encode};
use core::fmt;
use scale_info::TypeInfo;
use sp_core::H256;
use sp_domain_digests::AsPredigest;
use sp_domains::proof_provider_and_verifier::StorageProofVerifier;
use sp_domains::{
BundleValidity, DomainId, ExecutionReceiptFor, ExtrinsicDigest, HeaderHashFor,
HeaderHashingFor, InvalidBundleType,
};
use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT};
use sp_runtime::{Digest, DigestItem};
use sp_subspace_mmr::ConsensusChainMmrLeafProof;
use sp_trie::StorageProof;
use subspace_runtime_primitives::Balance;
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum ApplyExtrinsicMismatch {
StateRoot(u32),
Shorter,
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum FinalizeBlockMismatch {
StateRoot,
Longer(u32),
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum ExecutionPhase {
InitializeBlock,
ApplyExtrinsic {
extrinsic_proof: StorageProof,
mismatch: ApplyExtrinsicMismatch,
},
FinalizeBlock { mismatch: FinalizeBlockMismatch },
}
impl ExecutionPhase {
pub fn execution_method(&self) -> &'static str {
match self {
Self::InitializeBlock => "DomainCoreApi_initialize_block_with_post_state_root",
Self::ApplyExtrinsic { .. } => "DomainCoreApi_apply_extrinsic_with_post_state_root",
Self::FinalizeBlock { .. } => "BlockBuilder_finalize_block",
}
}
pub fn is_state_root_mismatch(&self) -> bool {
matches!(
self,
ExecutionPhase::InitializeBlock
| ExecutionPhase::ApplyExtrinsic {
mismatch: ApplyExtrinsicMismatch::StateRoot(_),
extrinsic_proof: _,
}
| ExecutionPhase::FinalizeBlock {
mismatch: FinalizeBlockMismatch::StateRoot,
}
)
}
pub fn decode_execution_result<Header: HeaderT>(
&self,
execution_result: Vec<u8>,
) -> Result<Header::Hash, VerificationError<Header::Hash>> {
match self {
Self::InitializeBlock | Self::ApplyExtrinsic { .. } => {
let encoded_storage_root = Vec::<u8>::decode(&mut execution_result.as_slice())
.map_err(VerificationError::InitializeBlockOrApplyExtrinsicDecode)?;
Header::Hash::decode(&mut encoded_storage_root.as_slice())
.map_err(VerificationError::StorageRootDecode)
}
Self::FinalizeBlock { .. } => {
let new_header = Header::decode(&mut execution_result.as_slice())
.map_err(VerificationError::HeaderDecode)?;
Ok(*new_header.state_root())
}
}
}
pub fn pre_post_state_root<CBlock, DomainHeader, Balance>(
&self,
bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
) -> Result<(H256, H256), VerificationError<DomainHeader::Hash>>
where
CBlock: BlockT,
DomainHeader: HeaderT,
DomainHeader::Hash: Into<H256>,
{
if bad_receipt.execution_trace.len() < 2 {
return Err(VerificationError::InvalidExecutionTrace);
}
let (pre, post) = match self {
ExecutionPhase::InitializeBlock => (
bad_receipt_parent.final_state_root,
bad_receipt.execution_trace[0],
),
ExecutionPhase::ApplyExtrinsic {
mismatch: ApplyExtrinsicMismatch::StateRoot(mismatch_index),
..
} => {
if *mismatch_index == 0
|| *mismatch_index >= bad_receipt.execution_trace.len() as u32 - 1
{
return Err(VerificationError::InvalidApplyExtrinsicTraceIndex);
}
(
bad_receipt.execution_trace[*mismatch_index as usize - 1],
bad_receipt.execution_trace[*mismatch_index as usize],
)
}
ExecutionPhase::ApplyExtrinsic {
mismatch: ApplyExtrinsicMismatch::Shorter,
..
} => {
let mismatch_index = bad_receipt.execution_trace.len() - 1;
(
bad_receipt.execution_trace[mismatch_index - 1],
bad_receipt.execution_trace[mismatch_index],
)
}
ExecutionPhase::FinalizeBlock {
mismatch: FinalizeBlockMismatch::StateRoot,
} => {
let mismatch_index = bad_receipt.execution_trace.len() - 1;
(
bad_receipt.execution_trace[mismatch_index - 1],
bad_receipt.execution_trace[mismatch_index],
)
}
ExecutionPhase::FinalizeBlock {
mismatch: FinalizeBlockMismatch::Longer(mismatch_index),
} => {
if *mismatch_index == 0
|| *mismatch_index >= bad_receipt.execution_trace.len() as u32 - 1
{
return Err(VerificationError::InvalidLongerMismatchTraceIndex);
}
(
bad_receipt.execution_trace[(*mismatch_index - 1) as usize],
bad_receipt.execution_trace[*mismatch_index as usize],
)
}
};
Ok((pre.into(), post.into()))
}
pub fn call_data<CBlock, DomainHeader, Balance>(
&self,
bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
) -> Result<Vec<u8>, VerificationError<DomainHeader::Hash>>
where
CBlock: BlockT,
DomainHeader: HeaderT,
{
Ok(match self {
ExecutionPhase::InitializeBlock => {
let inherent_digests = Digest {
logs: sp_std::vec![DigestItem::consensus_block_info(
bad_receipt.consensus_block_hash,
)],
};
let new_header = DomainHeader::new(
bad_receipt.domain_block_number,
Default::default(),
Default::default(),
bad_receipt_parent.domain_block_hash,
inherent_digests,
);
new_header.encode()
}
ExecutionPhase::ApplyExtrinsic {
extrinsic_proof: proof_of_inclusion,
mismatch,
} => {
let mismatch_index = match mismatch {
ApplyExtrinsicMismatch::StateRoot(mismatch_index) => *mismatch_index,
ApplyExtrinsicMismatch::Shorter => {
(bad_receipt.execution_trace.len() - 1) as u32
}
};
let extrinsic_index: u32 = mismatch_index - 1;
let storage_key =
StorageProofVerifier::<DomainHeader::Hashing>::enumerated_storage_key(
extrinsic_index,
);
StorageProofVerifier::<DomainHeader::Hashing>::get_bare_value(
&bad_receipt.domain_block_extrinsic_root,
proof_of_inclusion.clone(),
storage_key,
)
.map_err(|_| VerificationError::InvalidApplyExtrinsicCallData)?
}
ExecutionPhase::FinalizeBlock { .. } => Vec::new(),
})
}
}
#[derive(Debug, thiserror::Error)]
pub enum VerificationError<DomainHash> {
#[error("Failed to pass the execution proof check")]
BadExecutionProof,
#[error("The fraud proof prove nothing invalid")]
InvalidProof,
#[error("Failed to decode the return value of `initialize_block` and `apply_extrinsic`: {0}")]
InitializeBlockOrApplyExtrinsicDecode(codec::Error),
#[
error(
"Failed to decode the storage root from verifying `initialize_block` and `apply_extrinsic`: {0}"
)
]
StorageRootDecode(codec::Error),
#[error("Failed to decode the header from verifying `finalize_block`: {0}")]
HeaderDecode(codec::Error),
#[error("The receipt's execution_trace have less than 2 traces")]
InvalidExecutionTrace,
#[error("Invalid ApplyExtrinsic trace index")]
InvalidApplyExtrinsicTraceIndex,
#[error("Invalid longer mismatch trace index")]
InvalidLongerMismatchTraceIndex,
#[error("Invalid ApplyExtrinsic call data")]
InvalidApplyExtrinsicCallData,
#[error("Invalid Bundle Digest")]
InvalidBundleDigest,
#[error("Bundle with requested index not found in execution receipt")]
BundleNotFound,
#[error(
"Unexpected bundle entry at {bundle_index} in bad receipt found: \
{targeted_entry_bundle:?} with fraud proof's type of proof: \
{fraud_proof_invalid_type_of_proof:?}"
)]
UnexpectedTargetedBundleEntry {
bundle_index: u32,
fraud_proof_invalid_type_of_proof: InvalidBundleType,
targeted_entry_bundle: BundleValidity<DomainHash>,
},
#[error("Failed to derive bundle digest")]
FailedToDeriveBundleDigest,
#[error("The target valid bundle not found from the target bad receipt")]
TargetValidBundleNotFound,
#[error("Failed to check extrinsics in single context")]
FailedToCheckExtrinsicsInSingleContext,
#[error(
"Bad MMR proof, the proof is probably expired or is generated against a different fork"
)]
BadMmrProof,
#[error("Unexpected MMR proof")]
UnexpectedMmrProof,
#[error("Failed to verify storage proof")]
StorageProof(storage_proof::VerificationError),
#[error("Failed to derive domain inherent extrinsic")]
FailedToDeriveDomainInherentExtrinsic,
#[error("Failed to derive domain storage key")]
FailedToGetDomainStorageKey,
#[error("Unexpected invalid bundle proof data")]
UnexpectedInvalidBundleProofData,
#[error("Extrinsic with requested index not found in bundle")]
ExtrinsicNotFound,
#[error("Failed to get domain runtime call response")]
FailedToGetDomainRuntimeCallResponse,
#[error("Failed to get bundle weight")]
FailedToGetBundleWeight,
#[error("Failed to extract xdm mmr proof")]
FailedToGetExtractXdmMmrProof,
#[error("Failed to decode xdm mmr proof")]
FailedToDecodeXdmMmrProof,
}
impl<DomainHash> From<storage_proof::VerificationError> for VerificationError<DomainHash> {
fn from(err: storage_proof::VerificationError) -> Self {
Self::StorageProof(err)
}
}
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct FraudProof<Number, Hash, DomainHeader: HeaderT, MmrHash> {
pub domain_id: DomainId,
pub bad_receipt_hash: HeaderHashFor<DomainHeader>,
pub maybe_mmr_proof: Option<ConsensusChainMmrLeafProof<Number, Hash, MmrHash>>,
pub maybe_domain_runtime_code_proof: Option<DomainRuntimeCodeAt<Number, Hash, MmrHash>>,
pub proof: FraudProofVariant<Number, Hash, MmrHash, DomainHeader>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum FraudProofVariant<Number, Hash, MmrHash, DomainHeader: HeaderT> {
InvalidStateTransition(InvalidStateTransitionProof),
ValidBundle(ValidBundleProof<Number, Hash, DomainHeader>),
InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof),
InvalidBundles(InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader>),
InvalidDomainBlockHash(InvalidDomainBlockHashProof),
InvalidBlockFees(InvalidBlockFeesProof),
InvalidTransfers(InvalidTransfersProof),
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
Dummy,
}
impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash> {
pub fn domain_id(&self) -> DomainId {
self.domain_id
}
pub fn targeted_bad_receipt_hash(&self) -> HeaderHashFor<DomainHeader> {
self.bad_receipt_hash
}
pub fn is_unexpected_domain_runtime_code_proof(&self) -> bool {
self.maybe_domain_runtime_code_proof.is_some()
&& matches!(self.proof, FraudProofVariant::InvalidDomainBlockHash(_))
}
pub fn is_unexpected_mmr_proof(&self) -> bool {
if self.maybe_mmr_proof.is_none() {
return false;
}
!matches!(
self.proof,
FraudProofVariant::InvalidExtrinsicsRoot(_)
| FraudProofVariant::InvalidBundles(_)
| FraudProofVariant::ValidBundle(_)
)
}
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
pub fn dummy_fraud_proof(
domain_id: DomainId,
bad_receipt_hash: HeaderHashFor<DomainHeader>,
) -> FraudProof<Number, Hash, DomainHeader, MmrHash> {
Self {
domain_id,
bad_receipt_hash,
maybe_mmr_proof: None,
maybe_domain_runtime_code_proof: None,
proof: FraudProofVariant::Dummy,
}
}
}
impl<Number, Hash, MmrHash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader, MmrHash>
where
Number: Encode,
Hash: Encode,
MmrHash: Encode,
{
pub fn hash(&self) -> HeaderHashFor<DomainHeader> {
HeaderHashingFor::<DomainHeader>::hash(&self.encode())
}
}
impl<Number, Hash, MmrHash, DomainHeader: HeaderT> fmt::Debug
for FraudProof<Number, Hash, DomainHeader, MmrHash>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let fp_target =
scale_info::prelude::format!("{:?}#{:?}", self.domain_id, self.bad_receipt_hash);
match &self.proof {
FraudProofVariant::InvalidStateTransition(_) => {
write!(f, "InvalidStateTransitionFraudProof({fp_target})")
}
FraudProofVariant::InvalidExtrinsicsRoot(_) => {
write!(f, "InvalidExtrinsicsRootFraudProof({fp_target})")
}
FraudProofVariant::InvalidBlockFees(_) => {
write!(f, "InvalidBlockFeesFraudProof({fp_target})")
}
FraudProofVariant::ValidBundle(_) => {
write!(f, "ValidBundleFraudProof({fp_target})")
}
FraudProofVariant::InvalidBundles(proof) => {
write!(
f,
"InvalidBundlesFraudProof(type: {:?}, target: {fp_target})",
proof.invalid_bundle_type
)
}
FraudProofVariant::InvalidDomainBlockHash(_) => {
write!(f, "InvalidDomainBlockHashFraudProof({fp_target})")
}
FraudProofVariant::InvalidTransfers(_) => {
write!(f, "InvalidTransfersFraudProof({fp_target})")
}
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
FraudProofVariant::Dummy => {
write!(f, "DummyFraudProof({fp_target})")
}
}
}
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct ValidBundleDigest {
pub bundle_index: u32,
pub bundle_digest: Vec<(
Option<domain_runtime_primitives::opaque::AccountId>,
ExtrinsicDigest,
)>,
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct DomainRuntimeCodeAt<Number, Hash, MmrHash> {
pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
pub domain_runtime_code_proof: DomainRuntimeCodeProof,
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct InvalidStateTransitionProof {
pub execution_proof: StorageProof,
pub execution_phase: ExecutionPhase,
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct ValidBundleProof<Number, Hash, DomainHeader: HeaderT> {
pub bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct InvalidExtrinsicsRootProof {
pub valid_bundle_digests: Vec<ValidBundleDigest>,
pub block_randomness_proof: BlockRandomnessProof,
pub domain_inherent_extrinsic_data_proof: DomainInherentExtrinsicDataProof,
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct MmrRootProof<Number, Hash, MmrHash> {
pub mmr_proof: ConsensusChainMmrLeafProof<Number, Hash, MmrHash>,
pub mmr_root_storage_proof: MmrRootStorageProof<MmrHash>,
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader: HeaderT> {
Extrinsic(StorageProof),
Bundle(OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>),
BundleAndExecution {
bundle_with_proof: OpaqueBundleWithProof<Number, Hash, DomainHeader, Balance>,
execution_proof: StorageProof,
},
InvalidXDMProofData {
extrinsic_proof: StorageProof,
mmr_root_proof: Option<MmrRootProof<Number, Hash, MmrHash>>,
},
}
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct InvalidBundlesProof<Number, Hash, MmrHash, DomainHeader: HeaderT> {
pub bundle_index: u32,
pub invalid_bundle_type: InvalidBundleType,
pub is_true_invalid_fraud_proof: bool,
pub proof_data: InvalidBundlesProofData<Number, Hash, MmrHash, DomainHeader>,
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct InvalidBlockFeesProof {
pub storage_proof: StorageProof,
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct InvalidTransfersProof {
pub storage_proof: StorageProof,
}
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct InvalidDomainBlockHashProof {
pub digest_storage_proof: StorageProof,
}