Skip to content
Merged
41 changes: 25 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ members = [
"cumulus/client/consensus/common",
"cumulus/client/consensus/relay-chain",
"cumulus/client/executor-gossip",
"cumulus/client/fraud-proof",
"cumulus/pallets/executive",
"cumulus/parachain-template/node",
"cumulus/parachain-template/runtime",
Expand Down
9 changes: 2 additions & 7 deletions crates/pallet-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ mod pallet {
}
Call::submit_fraud_proof { fraud_proof } => {
// TODO: prevent the spamming of fraud proof transaction.
if let Err(e) = Self::check_fraud_proof(fraud_proof) {
log::error!(target: "runtime::subspace::executor", "Invalid fraud proof: {:?}", e);
if !sp_executor::fraud_proof_ext::fraud_proof::verify(fraud_proof) {
log::error!(target: "runtime::subspace::executor", "Invalid fraud proof: {:?}", fraud_proof);
return InvalidTransaction::Custom(INVALID_FRAUD_PROOF).into();
}
// TODO: proper tag value.
Expand Down Expand Up @@ -264,11 +264,6 @@ mod pallet {
}

impl<T: Config> Pallet<T> {
// TODO: Checks if the fraud proof is valid.
fn check_fraud_proof(_fraud_proof: &FraudProof) -> Result<(), Error<T>> {
Ok(())
}

// TODO: Checks if the bundle equivocation proof is valid.
fn check_bundle_equivocation_proof(
_bundle_equivocation_proof: &BundleEquivocationProof,
Expand Down
6 changes: 6 additions & 0 deletions crates/sp-executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ scale-info = { version = "2.0.1", default-features = false, features = ["derive"
sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-consensus-slots = { version = "0.10.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-externalities = { version = "0.12.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-runtime-interface = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-state-machine = { version = "0.12.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-std = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-trie = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
subspace-core-primitives = { version = "0.1.0", default-features = false, path = "../subspace-core-primitives" }
Expand All @@ -31,7 +34,10 @@ std = [
"sp-api/std",
"sp-consensus-slots/std",
"sp-core/std",
"sp-externalities/std",
"sp-runtime/std",
"sp-runtime-interface/std",
"sp-state-machine/std",
"sp-std/std",
"sp-trie/std",
"subspace-core-primitives/std",
Expand Down
134 changes: 133 additions & 1 deletion crates/sp-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_consensus_slots::Slot;
use sp_core::H256;
use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
use sp_runtime::traits::{BlakeTwo256, Hash as HashT, Header as HeaderT};
use sp_runtime::{OpaqueExtrinsic, RuntimeDebug};
use sp_runtime_interface::pass_by::PassBy;
use sp_std::borrow::Cow;
use sp_std::vec::Vec;
use sp_trie::StorageProof;
Expand Down Expand Up @@ -125,15 +126,112 @@ impl<Hash: Encode> From<ExecutionReceipt<Hash>> for OpaqueExecutionReceipt {
}
}

/// Execution phase along with an optional encoded call data.
///
/// Each execution phase has a different method for the runtime call.
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone, RuntimeDebug)]
pub enum ExecutionPhase {
/// Executes the `initialize_block` hook.
InitializeBlock { call_data: Vec<u8> },
/// Executes some extrinsic.
/// TODO: maybe optimized to not include the whole extrinsic blob in the future.
ApplyExtrinsic { call_data: Vec<u8> },
/// Executes the `finalize_block` hook.
FinalizeBlock,
}

impl ExecutionPhase {
/// Returns the method for generating the proof.
pub fn proving_method(&self) -> &'static str {
match self {
// TODO: Replace `SecondaryApi_initialize_block_with_post_state_root` with `Core_initalize_block`
// Should be a same issue with https://github.com/paritytech/substrate/pull/10922#issuecomment-1068997467
Self::InitializeBlock { .. } => "SecondaryApi_initialize_block_with_post_state_root",
Self::ApplyExtrinsic { .. } => "BlockBuilder_apply_extrinsic",
Self::FinalizeBlock => "BlockBuilder_finalize_block",
}
}

/// Returns the method for verifying the proof.
///
/// The difference with [`Self::proving_method`] is that the return value of verifying method
/// must contain the post state root info so that it can be used to compare whether the
/// result of execution reported in [`FraudProof`] is expected or not.
pub fn verifying_method(&self) -> &'static str {
match self {
Self::InitializeBlock { .. } => "SecondaryApi_initialize_block_with_post_state_root",
Self::ApplyExtrinsic { .. } => "SecondaryApi_apply_extrinsic_with_post_state_root",
Self::FinalizeBlock => "BlockBuilder_finalize_block",
}
}

/// Returns the call data used to generate and verify the proof.
pub fn call_data(&self) -> &[u8] {
match self {
Self::InitializeBlock { call_data } | Self::ApplyExtrinsic { call_data } => call_data,
Self::FinalizeBlock => Default::default(),
}
}

/// Returns the post state root for the given execution result.
pub fn decode_execution_result<Header: HeaderT>(
&self,
execution_result: Vec<u8>,
) -> Result<Header::Hash, VerificationError> {
match self {
ExecutionPhase::InitializeBlock { .. } | ExecutionPhase::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)
}
ExecutionPhase::FinalizeBlock => {
let new_header = Header::decode(&mut execution_result.as_slice())
.map_err(VerificationError::HeaderDecode)?;
Ok(*new_header.state_root())
}
}
}
}

/// Error type of fraud proof verification on primary node.
#[derive(RuntimeDebug)]
pub enum VerificationError {
/// Runtime code backend unavailable.
RuntimeCodeBackend,
/// Runtime code can not be fetched from the backend.
RuntimeCode(&'static str),
/// 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`].
BadPostStateRoot { expected: H256, got: H256 },
/// Failed to decode the return value of `initialize_block` and `apply_extrinsic`.
InitializeBlockOrApplyExtrinsicDecode(parity_scale_codec::Error),
/// Failed to decode the storage root produced by verifying `initialize_block` or `apply_extrinsic`.
StorageRootDecode(parity_scale_codec::Error),
/// Failed to decode the header produced by `finalize_block`.
HeaderDecode(parity_scale_codec::Error),
}

/// Fraud proof for the state computation.
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone, RuntimeDebug)]
pub struct FraudProof {
/// Parent hash of the block at which the invalid execution occurred.
///
/// Runtime code for this block's execution is retrieved on top of the parent block.
pub parent_hash: H256,
/// State root before the fraudulent transaction.
pub pre_state_root: H256,
/// State root after the fraudulent transaction.
pub post_state_root: H256,
/// Proof recorded during the computation.
pub proof: StorageProof,
/// Execution phase.
pub execution_phase: ExecutionPhase,
}

impl PassBy for FraudProof {
type PassBy = sp_runtime_interface::pass_by::Codec<Self>;
}

/// Represents a bundle equivocation proof. An equivocation happens when an executor
Expand Down Expand Up @@ -213,3 +311,37 @@ sp_api::decl_runtime_apis! {
fn execution_wasm_bundle() -> Cow<'static, [u8]>;
}
}

pub mod fraud_proof_ext {
use sp_externalities::ExternalitiesExt;
use sp_runtime_interface::runtime_interface;

/// Externalities for verifying fraud proof.
pub trait Externalities: Send {
/// Returns `true` when the proof is valid.
fn verify_fraud_proof(&self, proof: &crate::FraudProof) -> bool;
}

#[cfg(feature = "std")]
sp_externalities::decl_extension! {
/// An extension to verify the fraud proof.
pub struct FraudProofExt(Box<dyn Externalities>);
}

#[cfg(feature = "std")]
impl FraudProofExt {
pub fn new<E: Externalities + 'static>(fraud_proof: E) -> Self {
Self(Box::new(fraud_proof))
}
}

#[runtime_interface]
pub trait FraudProof {
/// Verify fraud proof.
fn verify(&mut self, proof: &crate::FraudProof) -> bool {
self.extension::<FraudProofExt>()
.expect("No `FraudProof` associated for the current context!")
.verify_fraud_proof(proof)
}
}
}
26 changes: 26 additions & 0 deletions crates/subspace-fraud-proof/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "subspace-fraud-proof"
version = "0.1.0"
authors = ["Liu-Cheng Xu <xuliuchengxlc@gmail.com>"]
edition = "2021"
license = "GPL-3.0-or-later"
homepage = "https://subspace.network"
repository = "https://github.com/subspace/subspace/"
description = "Subspace fraud proof utilities"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.1.2", features = ["derive"] }
hash-db = "0.15.2"
sc-client-api = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-api = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-blockchain = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-core = { version = "6.0.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-executor = { version = "0.1.0", path = "../sp-executor" }
sp-externalities = { version = "0.12.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-runtime = { version = "6.0.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-state-machine = { version = "0.12.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
sp-trie = { version = "6.0.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
tracing = "0.1.23"
Loading