Skip to content

Commit

Permalink
Revamp InvalidStateTransitionProof generation and verification
Browse files Browse the repository at this point in the history
Signed-off-by: linning <linningde25@gmail.com>
  • Loading branch information
NingLin-P committed Oct 7, 2023
1 parent 1e38232 commit 7cb278a
Show file tree
Hide file tree
Showing 5 changed files with 285 additions and 106 deletions.
41 changes: 14 additions & 27 deletions crates/sp-domains/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,45 +84,21 @@ pub trait DeriveExtrinsics<Moment> {
#[derive(Debug)]
#[cfg_attr(feature = "thiserror", derive(thiserror::Error))]
pub enum VerificationError {
/// `pre_state_root` in the invalid state transition proof is invalid.
#[cfg_attr(feature = "thiserror", error("invalid `pre_state_root`"))]
InvalidPreStateRoot,
/// Hash of the consensus block being challenged not found.
#[cfg_attr(feature = "thiserror", error("consensus block hash not found"))]
ConsensusBlockHashNotFound,
/// `post_state_root` not found in the state.
#[cfg_attr(feature = "thiserror", error("`post_state_root` not found"))]
PostStateRootNotFound,
/// `post_state_root` is same as the one stored on chain.
#[cfg_attr(
feature = "thiserror",
error("`post_state_root` is same as the one on chain")
)]
SamePostStateRoot,
/// Domain extrinsic at given index not found.
#[cfg_attr(
feature = "thiserror",
error("Domain extrinsic at index {0} not found")
)]
DomainExtrinsicNotFound(u32),
/// Error occurred while building the domain extrinsics.
#[cfg_attr(
feature = "thiserror",
error("Failed to rebuild the domain extrinsic list")
)]
FailedToBuildDomainExtrinsics,
/// Failed to pass the execution proof check.
#[cfg_attr(
feature = "thiserror",
error("Failed to pass the execution proof check")
)]
BadProof(sp_std::boxed::Box<dyn sp_state_machine::Error>),
/// The `post_state_root` calculated by farmer does not match the one declared in [`FraudProof`].
/// The resulted `post_state_root` is same as the one stored on chain.
#[cfg_attr(
feature = "thiserror",
error("`post_state_root` mismatches, expected: {expected}, got: {got}")
error("`post_state_root` is same as the one on chain")
)]
BadPostStateRoot { expected: H256, got: H256 },
SamePostStateRoot,
/// Failed to decode the return value of `initialize_block` and `apply_extrinsic`.
#[cfg_attr(
feature = "thiserror",
Expand Down Expand Up @@ -192,6 +168,17 @@ pub enum VerificationError {
error("Oneshot error when verifying fraud proof in tx pool: {0}")
)]
Oneshot(String),
#[cfg_attr(
feature = "thiserror",
error("The receipt's execution_trace have less than 2 traces")
)]
InvalidExecutionTrace,
#[cfg_attr(feature = "thiserror", error("State root not using H256"))]
InvalidStateRootType,
#[cfg_attr(feature = "thiserror", error("Invalid ApplyExtrinsic trace index"))]
InvalidApplyExtrinsicTraceIndex,
#[cfg_attr(feature = "thiserror", error("Invalid ApplyExtrinsic call data"))]
InvalidApplyExtrinsicCallData,
}

// TODO: Define rest of the fraud proof fields
Expand Down
220 changes: 201 additions & 19 deletions crates/subspace-fraud-proof/src/invalid_state_transition_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@
//! block execution, block execution hooks (`initialize_block` and `finalize_block`) and any
//! specific extrinsic execution are supported.
use crate::domain_runtime_code::DomainRuntimeCode;
use crate::verifier_api::VerifierApi;
use crate::ExecutionReceiptFor;
use codec::{Codec, Decode, Encode};
use domain_runtime_primitives::opaque::Block;
use hash_db::{HashDB, Hasher, Prefix};
use sc_client_api::backend;
use sp_api::{ProvideRuntimeApi, StorageProof};
use sp_blockchain::HeaderBackend;
use sp_core::traits::{CodeExecutor, RuntimeCode};
use sp_core::H256;
use sp_domain_digests::AsPredigest;
use sp_domains::fraud_proof::{ExecutionPhase, InvalidStateTransitionProof, VerificationError};
use sp_domains::valued_trie_root::verify_proof;
use sp_domains::DomainsApi;
use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor};
use sp_runtime::Digest;
use sp_runtime::{Digest, DigestItem};
use sp_state_machine::backend::AsTrieBackend;
use sp_state_machine::{TrieBackend, TrieBackendBuilder, TrieBackendStorage};
use sp_trie::DBValue;
use sp_trie::{DBValue, LayoutV1};
use std::marker::PhantomData;
use std::sync::Arc;

Expand Down Expand Up @@ -178,15 +182,15 @@ where
}

/// Invalid state transition proof verifier.
pub struct InvalidStateTransitionProofVerifier<CBlock, CClient, Exec, Hash, VerifierClient> {
pub struct InvalidStateTransitionProofVerifier<CBlock, DomainBlock, CClient, Exec, VerifierClient> {
consensus_client: Arc<CClient>,
executor: Exec,
verifier_client: VerifierClient,
_phantom: PhantomData<(CBlock, Hash)>,
_phantom: PhantomData<(CBlock, DomainBlock)>,
}

impl<CBlock, CClient, Exec, Hash, VerifierClient> Clone
for InvalidStateTransitionProofVerifier<CBlock, CClient, Exec, Hash, VerifierClient>
impl<CBlock, DomainBlock, CClient, Exec, VerifierClient> Clone
for InvalidStateTransitionProofVerifier<CBlock, DomainBlock, CClient, Exec, VerifierClient>
where
Exec: Clone,
VerifierClient: Clone,
Expand All @@ -201,15 +205,15 @@ where
}
}

impl<CBlock, CClient, Exec, Hash, VerifierClient>
InvalidStateTransitionProofVerifier<CBlock, CClient, Exec, Hash, VerifierClient>
impl<CBlock, DomainBlock, CClient, Exec, VerifierClient>
InvalidStateTransitionProofVerifier<CBlock, DomainBlock, CClient, Exec, VerifierClient>
where
CBlock: BlockT,
DomainBlock: BlockT,
H256: Into<CBlock::Hash>,
CClient: ProvideRuntimeApi<CBlock> + Send + Sync,
CClient::Api: DomainsApi<CBlock, domain_runtime_primitives::BlockNumber, Hash>,
CClient: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
CClient::Api: DomainsApi<CBlock, NumberFor<DomainBlock>, <DomainBlock as BlockT>::Hash>,
Exec: CodeExecutor + Clone + 'static,
Hash: Encode + Decode,
VerifierClient: VerifierApi,
{
/// Constructs a new instance of [`InvalidStateTransitionProofVerifier`].
Expand All @@ -222,16 +226,194 @@ where
consensus_client,
executor,
verifier_client,
_phantom: PhantomData::<(CBlock, Hash)>,
_phantom: PhantomData::<(CBlock, DomainBlock)>,
}
}

fn get_pre_post_state_root(
&self,
execution_phase: &ExecutionPhase,
bad_receipt: &ExecutionReceiptFor<DomainBlock, CBlock>,
bad_receipt_parent: &ExecutionReceiptFor<DomainBlock, CBlock>,
) -> Result<(H256, H256), VerificationError> {
if bad_receipt.execution_trace.len() < 2 {
return Err(VerificationError::InvalidExecutionTrace);
}
let (pre, post) = match execution_phase {
ExecutionPhase::InitializeBlock => (
bad_receipt_parent.final_state_root,
bad_receipt.execution_trace[0],
),
ExecutionPhase::ApplyExtrinsic { 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::FinalizeBlock => {
let mismatch_index = bad_receipt.execution_trace.len() - 1;
(
bad_receipt.execution_trace[mismatch_index - 1],
bad_receipt.execution_trace[mismatch_index],
)
}
};
// TODO: avoid the encode & decode?
let as_h256 = |state_root: &<DomainBlock as BlockT>::Hash| {
H256::decode(&mut state_root.encode().as_slice())
.map_err(|_| VerificationError::InvalidStateRootType)
};
Ok((as_h256(&pre)?, as_h256(&post)?))
}

fn get_call_data(
&self,
execution_phase: &ExecutionPhase,
bad_receipt: &ExecutionReceiptFor<DomainBlock, CBlock>,
bad_receipt_parent: &ExecutionReceiptFor<DomainBlock, CBlock>,
) -> Result<Vec<u8>, VerificationError> {
Ok(match execution_phase {
ExecutionPhase::InitializeBlock => {
let digest = Digest {
logs: vec![DigestItem::consensus_block_info(
bad_receipt.consensus_block_hash,
)],
};
let new_header = <DomainBlock as BlockT>::Header::new(
bad_receipt.domain_block_number,
Default::default(),
Default::default(),
bad_receipt_parent.domain_block_hash,
digest,
);
new_header.encode()
}
ExecutionPhase::ApplyExtrinsic {
proof_of_inclusion,
mismatch_index,
extrinsic,
} => {
if !verify_proof::<LayoutV1<BlakeTwo256>>(
proof_of_inclusion,
&bad_receipt.domain_block_extrinsic_root,
extrinsic.clone(),
*mismatch_index,
) {
return Err(VerificationError::InvalidApplyExtrinsicCallData);
}

extrinsic.clone()
}
ExecutionPhase::FinalizeBlock => Vec::new(),
})
}

/// Verifies the invalid state transition proof.
pub fn verify(
&self,
invalid_state_transition_proof: &InvalidStateTransitionProof,
) -> Result<(), VerificationError> {
todo!()
let (bad_receipt, bad_receipt_parent) = {
let consensus_hash = self.consensus_client.info().best_hash;
let bad_receipt_hash = invalid_state_transition_proof.bad_receipt_hash;
let bad_receipt = self.consensus_client
.runtime_api()
.execution_receipt(consensus_hash, bad_receipt_hash)?
.ok_or_else(|| {
sp_blockchain::Error::Application(format!(
"Bad receipt {bad_receipt_hash} not found in canonical chain {consensus_hash}, \
it may already been pruned or exist in a fork"
).into())
})?;
let bad_receipt_parent = self
.consensus_client
.runtime_api()
.execution_receipt(consensus_hash, bad_receipt.parent_domain_block_receipt_hash)?
.ok_or_else(|| {
sp_blockchain::Error::Application(
format!(
"Bad receipt parent {} not found in canonical chain {consensus_hash}, \
it may already expired and being pruned",
bad_receipt.parent_domain_block_receipt_hash
)
.into(),
)
})?;
(bad_receipt, bad_receipt_parent)
};
let consensus_block_header = {
let consensus_block_hash = bad_receipt.consensus_block_hash;
self.consensus_client
.header(consensus_block_hash)?
.ok_or_else(|| {
sp_blockchain::Error::Backend(format!(
"Header for {consensus_block_hash} not found"
))
})?
};
let parent_consensus_block_header = {
let parent_hash = consensus_block_header.parent_hash();
self.consensus_client.header(*parent_hash)?.ok_or_else(|| {
sp_blockchain::Error::Backend(format!("Header for {parent_hash} not found"))
})?
};

let InvalidStateTransitionProof {
domain_id,
runtime_code_with_proof,
proof,
execution_phase,
..
} = invalid_state_transition_proof;

// Get the pre/post state root from the `bad_receipt`
let (pre_state_root, post_state_root) =
self.get_pre_post_state_root(execution_phase, &bad_receipt, &bad_receipt_parent)?;

// Verify the existence of the `domain_runtime_code` in the consensus chain
//
// NOTE: we use the state root of the parent block to verify here, see the comment
// of `DomainRuntimeCodeWithProof` for more detail.
let domain_runtime_code = {
let wasm_bundle = runtime_code_with_proof
.verify::<CBlock>(*domain_id, parent_consensus_block_header.state_root())?;
DomainRuntimeCode { wasm_bundle }
};

let runtime_code = RuntimeCode {
code_fetcher: &domain_runtime_code.as_runtime_code_fetcher(),
hash: b"Hash of the code does not matter in terms of the execution proof check"
.to_vec(),
heap_pages: None,
};

let call_data = self.get_call_data(execution_phase, &bad_receipt, &bad_receipt_parent)?;

let execution_result = sp_state_machine::execution_proof_check::<BlakeTwo256, _>(
pre_state_root,
proof.clone(),
&mut Default::default(),
&self.executor,
execution_phase.verifying_method(),
&call_data,
&runtime_code,
)
.map_err(VerificationError::BadProof)?;

let valid_post_state_root =
execution_phase.decode_execution_result::<CBlock::Header>(execution_result)?;
let valid_post_state_root = H256::decode(&mut valid_post_state_root.encode().as_slice())?;

if valid_post_state_root != post_state_root {
Ok(())
} else {
Err(VerificationError::SamePostStateRoot)
}
}
}

Expand All @@ -244,15 +426,15 @@ pub trait VerifyInvalidStateTransitionProof {
) -> Result<(), VerificationError>;
}

impl<CBlock, C, Exec, Hash, VerifierClient> VerifyInvalidStateTransitionProof
for InvalidStateTransitionProofVerifier<CBlock, C, Exec, Hash, VerifierClient>
impl<CBlock, DomainBlock, C, Exec, VerifierClient> VerifyInvalidStateTransitionProof
for InvalidStateTransitionProofVerifier<CBlock, DomainBlock, C, Exec, VerifierClient>
where
CBlock: BlockT,
DomainBlock: BlockT,
H256: Into<CBlock::Hash>,
C: ProvideRuntimeApi<CBlock> + Send + Sync,
C::Api: DomainsApi<CBlock, domain_runtime_primitives::BlockNumber, Hash>,
C: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
C::Api: DomainsApi<CBlock, NumberFor<DomainBlock>, <DomainBlock as BlockT>::Hash>,
Exec: CodeExecutor + Clone + 'static,
Hash: Encode + Decode,
VerifierClient: VerifierApi,
{
fn verify_invalid_state_transition_proof(
Expand Down
10 changes: 10 additions & 0 deletions crates/subspace-fraud-proof/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,26 @@ pub mod invalid_transaction_proof;
mod tests;
pub mod verifier_api;

use domain_runtime_primitives::Balance;
use futures::channel::oneshot;
use futures::FutureExt;
use invalid_state_transition_proof::VerifyInvalidStateTransitionProof;
use invalid_transaction_proof::VerifyInvalidTransactionProof;
use sp_core::traits::SpawnNamed;
use sp_domains::fraud_proof::{FraudProof, VerificationError};
use sp_domains::ExecutionReceipt;
use sp_runtime::traits::{Block as BlockT, NumberFor};
use std::marker::PhantomData;
use std::sync::Arc;

type ExecutionReceiptFor<Block, CBlock> = ExecutionReceipt<
NumberFor<CBlock>,
<CBlock as BlockT>::Hash,
NumberFor<Block>,
<Block as BlockT>::Hash,
Balance,
>;

/// Verify fraud proof.
///
/// Verifier is either the primary chain client or the system domain client.
Expand Down
Loading

0 comments on commit 7cb278a

Please sign in to comment.