Skip to content

Commit 13be6b6

Browse files
authored
Merge pull request #301 from subspace/fraud-proof-verification
Fraud proof verification on the primary node
2 parents 55e1dfe + 1736c85 commit 13be6b6

File tree

18 files changed

+598
-288
lines changed

18 files changed

+598
-288
lines changed

Cargo.lock

Lines changed: 25 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ members = [
99
"cumulus/client/consensus/common",
1010
"cumulus/client/consensus/relay-chain",
1111
"cumulus/client/executor-gossip",
12-
"cumulus/client/fraud-proof",
1312
"cumulus/pallets/executive",
1413
"cumulus/parachain-template/node",
1514
"cumulus/parachain-template/runtime",

crates/pallet-executor/src/lib.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,8 @@ mod pallet {
212212
}
213213
Call::submit_fraud_proof { fraud_proof } => {
214214
// TODO: prevent the spamming of fraud proof transaction.
215-
if let Err(e) = Self::check_fraud_proof(fraud_proof) {
216-
log::error!(target: "runtime::subspace::executor", "Invalid fraud proof: {:?}", e);
215+
if !sp_executor::fraud_proof_ext::fraud_proof::verify(fraud_proof) {
216+
log::error!(target: "runtime::subspace::executor", "Invalid fraud proof: {:?}", fraud_proof);
217217
return InvalidTransaction::Custom(INVALID_FRAUD_PROOF).into();
218218
}
219219
// TODO: proper tag value.
@@ -264,11 +264,6 @@ mod pallet {
264264
}
265265

266266
impl<T: Config> Pallet<T> {
267-
// TODO: Checks if the fraud proof is valid.
268-
fn check_fraud_proof(_fraud_proof: &FraudProof) -> Result<(), Error<T>> {
269-
Ok(())
270-
}
271-
272267
// TODO: Checks if the bundle equivocation proof is valid.
273268
fn check_bundle_equivocation_proof(
274269
_bundle_equivocation_proof: &BundleEquivocationProof,

crates/sp-executor/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ scale-info = { version = "2.0.1", default-features = false, features = ["derive"
1717
sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
1818
sp-consensus-slots = { version = "0.10.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
1919
sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
20+
sp-externalities = { version = "0.12.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
2021
sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
22+
sp-runtime-interface = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
23+
sp-state-machine = { version = "0.12.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
2124
sp-std = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
2225
sp-trie = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
2326
subspace-core-primitives = { version = "0.1.0", default-features = false, path = "../subspace-core-primitives" }
@@ -31,7 +34,10 @@ std = [
3134
"sp-api/std",
3235
"sp-consensus-slots/std",
3336
"sp-core/std",
37+
"sp-externalities/std",
3438
"sp-runtime/std",
39+
"sp-runtime-interface/std",
40+
"sp-state-machine/std",
3541
"sp-std/std",
3642
"sp-trie/std",
3743
"subspace-core-primitives/std",

crates/sp-executor/src/lib.rs

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ use parity_scale_codec::{Decode, Encode};
2121
use scale_info::TypeInfo;
2222
use sp_consensus_slots::Slot;
2323
use sp_core::H256;
24-
use sp_runtime::traits::{BlakeTwo256, Hash as HashT};
24+
use sp_runtime::traits::{BlakeTwo256, Hash as HashT, Header as HeaderT};
2525
use sp_runtime::{OpaqueExtrinsic, RuntimeDebug};
26+
use sp_runtime_interface::pass_by::PassBy;
2627
use sp_std::borrow::Cow;
2728
use sp_std::vec::Vec;
2829
use sp_trie::StorageProof;
@@ -125,15 +126,112 @@ impl<Hash: Encode> From<ExecutionReceipt<Hash>> for OpaqueExecutionReceipt {
125126
}
126127
}
127128

129+
/// Execution phase along with an optional encoded call data.
130+
///
131+
/// Each execution phase has a different method for the runtime call.
132+
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone, RuntimeDebug)]
133+
pub enum ExecutionPhase {
134+
/// Executes the `initialize_block` hook.
135+
InitializeBlock { call_data: Vec<u8> },
136+
/// Executes some extrinsic.
137+
/// TODO: maybe optimized to not include the whole extrinsic blob in the future.
138+
ApplyExtrinsic { call_data: Vec<u8> },
139+
/// Executes the `finalize_block` hook.
140+
FinalizeBlock,
141+
}
142+
143+
impl ExecutionPhase {
144+
/// Returns the method for generating the proof.
145+
pub fn proving_method(&self) -> &'static str {
146+
match self {
147+
// TODO: Replace `SecondaryApi_initialize_block_with_post_state_root` with `Core_initalize_block`
148+
// Should be a same issue with https://github.com/paritytech/substrate/pull/10922#issuecomment-1068997467
149+
Self::InitializeBlock { .. } => "SecondaryApi_initialize_block_with_post_state_root",
150+
Self::ApplyExtrinsic { .. } => "BlockBuilder_apply_extrinsic",
151+
Self::FinalizeBlock => "BlockBuilder_finalize_block",
152+
}
153+
}
154+
155+
/// Returns the method for verifying the proof.
156+
///
157+
/// The difference with [`Self::proving_method`] is that the return value of verifying method
158+
/// must contain the post state root info so that it can be used to compare whether the
159+
/// result of execution reported in [`FraudProof`] is expected or not.
160+
pub fn verifying_method(&self) -> &'static str {
161+
match self {
162+
Self::InitializeBlock { .. } => "SecondaryApi_initialize_block_with_post_state_root",
163+
Self::ApplyExtrinsic { .. } => "SecondaryApi_apply_extrinsic_with_post_state_root",
164+
Self::FinalizeBlock => "BlockBuilder_finalize_block",
165+
}
166+
}
167+
168+
/// Returns the call data used to generate and verify the proof.
169+
pub fn call_data(&self) -> &[u8] {
170+
match self {
171+
Self::InitializeBlock { call_data } | Self::ApplyExtrinsic { call_data } => call_data,
172+
Self::FinalizeBlock => Default::default(),
173+
}
174+
}
175+
176+
/// Returns the post state root for the given execution result.
177+
pub fn decode_execution_result<Header: HeaderT>(
178+
&self,
179+
execution_result: Vec<u8>,
180+
) -> Result<Header::Hash, VerificationError> {
181+
match self {
182+
ExecutionPhase::InitializeBlock { .. } | ExecutionPhase::ApplyExtrinsic { .. } => {
183+
let encoded_storage_root = Vec::<u8>::decode(&mut execution_result.as_slice())
184+
.map_err(VerificationError::InitializeBlockOrApplyExtrinsicDecode)?;
185+
Header::Hash::decode(&mut encoded_storage_root.as_slice())
186+
.map_err(VerificationError::StorageRootDecode)
187+
}
188+
ExecutionPhase::FinalizeBlock => {
189+
let new_header = Header::decode(&mut execution_result.as_slice())
190+
.map_err(VerificationError::HeaderDecode)?;
191+
Ok(*new_header.state_root())
192+
}
193+
}
194+
}
195+
}
196+
197+
/// Error type of fraud proof verification on primary node.
198+
#[derive(RuntimeDebug)]
199+
pub enum VerificationError {
200+
/// Runtime code backend unavailable.
201+
RuntimeCodeBackend,
202+
/// Runtime code can not be fetched from the backend.
203+
RuntimeCode(&'static str),
204+
/// Failed to pass the execution proof check.
205+
BadProof(sp_std::boxed::Box<dyn sp_state_machine::Error>),
206+
/// The `post_state_root` calculated by farmer does not match the one declared in [`FraudProof`].
207+
BadPostStateRoot { expected: H256, got: H256 },
208+
/// Failed to decode the return value of `initialize_block` and `apply_extrinsic`.
209+
InitializeBlockOrApplyExtrinsicDecode(parity_scale_codec::Error),
210+
/// Failed to decode the storage root produced by verifying `initialize_block` or `apply_extrinsic`.
211+
StorageRootDecode(parity_scale_codec::Error),
212+
/// Failed to decode the header produced by `finalize_block`.
213+
HeaderDecode(parity_scale_codec::Error),
214+
}
215+
128216
/// Fraud proof for the state computation.
129217
#[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone, RuntimeDebug)]
130218
pub struct FraudProof {
219+
/// Parent hash of the block at which the invalid execution occurred.
220+
///
221+
/// Runtime code for this block's execution is retrieved on top of the parent block.
222+
pub parent_hash: H256,
131223
/// State root before the fraudulent transaction.
132224
pub pre_state_root: H256,
133225
/// State root after the fraudulent transaction.
134226
pub post_state_root: H256,
135227
/// Proof recorded during the computation.
136228
pub proof: StorageProof,
229+
/// Execution phase.
230+
pub execution_phase: ExecutionPhase,
231+
}
232+
233+
impl PassBy for FraudProof {
234+
type PassBy = sp_runtime_interface::pass_by::Codec<Self>;
137235
}
138236

139237
/// Represents a bundle equivocation proof. An equivocation happens when an executor
@@ -213,3 +311,37 @@ sp_api::decl_runtime_apis! {
213311
fn execution_wasm_bundle() -> Cow<'static, [u8]>;
214312
}
215313
}
314+
315+
pub mod fraud_proof_ext {
316+
use sp_externalities::ExternalitiesExt;
317+
use sp_runtime_interface::runtime_interface;
318+
319+
/// Externalities for verifying fraud proof.
320+
pub trait Externalities: Send {
321+
/// Returns `true` when the proof is valid.
322+
fn verify_fraud_proof(&self, proof: &crate::FraudProof) -> bool;
323+
}
324+
325+
#[cfg(feature = "std")]
326+
sp_externalities::decl_extension! {
327+
/// An extension to verify the fraud proof.
328+
pub struct FraudProofExt(Box<dyn Externalities>);
329+
}
330+
331+
#[cfg(feature = "std")]
332+
impl FraudProofExt {
333+
pub fn new<E: Externalities + 'static>(fraud_proof: E) -> Self {
334+
Self(Box::new(fraud_proof))
335+
}
336+
}
337+
338+
#[runtime_interface]
339+
pub trait FraudProof {
340+
/// Verify fraud proof.
341+
fn verify(&mut self, proof: &crate::FraudProof) -> bool {
342+
self.extension::<FraudProofExt>()
343+
.expect("No `FraudProof` associated for the current context!")
344+
.verify_fraud_proof(proof)
345+
}
346+
}
347+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "subspace-fraud-proof"
3+
version = "0.1.0"
4+
authors = ["Liu-Cheng Xu <[email protected]>"]
5+
edition = "2021"
6+
license = "GPL-3.0-or-later"
7+
homepage = "https://subspace.network"
8+
repository = "https://github.com/subspace/subspace/"
9+
description = "Subspace fraud proof utilities"
10+
11+
[package.metadata.docs.rs]
12+
targets = ["x86_64-unknown-linux-gnu"]
13+
14+
[dependencies]
15+
codec = { package = "parity-scale-codec", version = "3.1.2", features = ["derive"] }
16+
hash-db = "0.15.2"
17+
sc-client-api = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
18+
sp-api = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
19+
sp-blockchain = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
20+
sp-core = { version = "6.0.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
21+
sp-executor = { version = "0.1.0", path = "../sp-executor" }
22+
sp-externalities = { version = "0.12.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
23+
sp-runtime = { version = "6.0.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
24+
sp-state-machine = { version = "0.12.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
25+
sp-trie = { version = "6.0.0", git = "https://github.com/paritytech/substrate", rev = "c364008a6c7da8456e17967f55edf51e45146998" }
26+
tracing = "0.1.23"

0 commit comments

Comments
 (0)