Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement OutOfRangeTx variant of Invalid bundle fraud proof #2039

Closed
wants to merge 9 commits into from
9 changes: 6 additions & 3 deletions crates/sp-domains/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ pub enum MissingBundleAdditionalData {
pub struct MissingInvalidBundleEntryFraudProof<Number, Hash, DomainNumber, DomainHash> {
pub domain_id: DomainId,
pub bad_receipt_hash: H256,
pub consensus_block_hash: Hash,
pub consensus_block_incl_bundle: Hash,
pub consensus_block_incl_er: Hash,
pub bundle_index: u32,
pub opaque_bundle_with_proof:
OpaqueBundleWithProof<Number, Hash, DomainNumber, DomainHash, Balance>,
Expand All @@ -217,7 +218,8 @@ impl<Number, Hash, DomainNumber, DomainHash>
pub fn new(
domain_id: DomainId,
bad_receipt_hash: H256,
consensus_block_hash: Hash,
consensus_block_incl_bundle: Hash,
consensus_block_incl_er: Hash,
bundle_index: u32,
opaque_bundle_with_proof: OpaqueBundleWithProof<
Number,
Expand All @@ -232,7 +234,8 @@ impl<Number, Hash, DomainNumber, DomainHash>
Self {
domain_id,
bad_receipt_hash,
consensus_block_hash,
consensus_block_incl_er,
consensus_block_incl_bundle,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fraud proof is targetting an invalid ER, consensus_block_incl_bundle should be ER::consensus_block_hash and why do we need consensus_block_incl_er?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is okay to use the best consensus hash to get the bad receipt, because the fraud proof is included in the next consensus block that builds on top of the best consensus block so the fraud proof should be verified against the best consensus block, if the bad receipt don't exist here which means the bad receipt is already pruned or is in other forks, in both cases the fraud proof should be considered as invalid.

bundle_index,
opaque_bundle_with_proof,
runtime_code_with_proof,
Expand Down
2 changes: 2 additions & 0 deletions crates/subspace-fraud-proof/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ frame-support = { version = "4.0.0-dev", git = "https://github.com/subspace/polk
futures = "0.3.28"
hash-db = "0.16.0"
sc-client-api = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "20be5f33a3d2b3f4b31a894f9829184b29fba3ef" }
sc-executor = { version = "0.10.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "20be5f33a3d2b3f4b31a894f9829184b29fba3ef" }
sp-api = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "20be5f33a3d2b3f4b31a894f9829184b29fba3ef" }
sp-blockchain = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "20be5f33a3d2b3f4b31a894f9829184b29fba3ef" }
sp-core = { version = "21.0.0", git = "https://github.com/subspace/polkadot-sdk", rev = "20be5f33a3d2b3f4b31a894f9829184b29fba3ef" }
Expand All @@ -32,6 +33,7 @@ subspace-core-primitives = { version = "0.1.0", path = "../../crates/subspace-co
tracing = "0.1.37"

[dev-dependencies]
domain-client-operator = { version = "0.1.0", path = "../../domains/client/domain-operator" }
domain-block-builder = { version = "0.1.0", path = "../../domains/client/block-builder" }
domain-test-service = { version = "0.1.0", path = "../../domains/test/service" }
pallet-balances = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "20be5f33a3d2b3f4b31a894f9829184b29fba3ef" }
Expand Down
51 changes: 30 additions & 21 deletions crates/subspace-fraud-proof/src/invalid_bundles_fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use codec::{Decode, Encode};
use domain_block_preprocessor::runtime_api_light::RuntimeApiLight;
use sc_executor::RuntimeVersionOf;
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_core::traits::CodeExecutor;
Expand Down Expand Up @@ -43,7 +44,7 @@ where
CClient: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
CClient::Api: DomainsApi<CBlock, NumberFor<DomainBlock>, <DomainBlock as BlockT>::Hash>
+ ExecutionReceiptApi<CBlock, NumberFor<DomainBlock>, <DomainBlock as BlockT>::Hash>,
Exec: CodeExecutor + 'static,
Exec: CodeExecutor + 'static + RuntimeVersionOf,
{
/// Constructs a new instance of [`InvalidBundleProofVerifier`].
pub fn new(consensus_client: Arc<CClient>, executor: Arc<Exec>) -> Self {
Expand All @@ -68,7 +69,8 @@ where
InvalidBundlesFraudProof::MissingInvalidBundleEntry(proof) => {
let MissingInvalidBundleEntryFraudProof {
domain_id,
consensus_block_hash,
consensus_block_incl_bundle: consensus_block_hash_incl_bundle,
consensus_block_incl_er: consensus_block_hash_incl_er,
runtime_code_with_proof,
opaque_bundle_with_proof,
additional_data,
Expand All @@ -77,17 +79,17 @@ where
..
} = proof;

let consensus_block_header = {
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();
let consensus_block_header_incl_bundle = self
.consensus_client
.header(*consensus_block_hash_incl_bundle)?
.ok_or_else(|| {
sp_blockchain::Error::Backend(format!(
"Header for {consensus_block_hash_incl_bundle} not found"
))
})?;

let parent_consensus_block_header_incl_bundle = {
let parent_hash = consensus_block_header_incl_bundle.parent_hash();
self.consensus_client.header(*parent_hash)?.ok_or_else(|| {
sp_blockchain::Error::Backend(format!("Header for {parent_hash} not found"))
})?
Expand All @@ -98,23 +100,28 @@ where
let bad_receipt = self
.consensus_client
.runtime_api()
.get_execution_receipt_by_hash(*consensus_block_hash, *bad_receipt_hash)?
.get_execution_receipt_by_hash(
*consensus_block_hash_incl_er,
*bad_receipt_hash,
)?
.ok_or(VerificationError::ExecutionReceiptNotFound)?;

let parent_of_bad_receipt = self
.consensus_client
.runtime_api()
.get_execution_receipt_by_hash(
*consensus_block_hash,
*consensus_block_hash_incl_er,
bad_receipt.parent_domain_block_receipt_hash,
)?
.ok_or(VerificationError::ExecutionReceiptNotFound)?;

let parent_domain_block_hash = parent_of_bad_receipt.domain_block_hash;

// Verify the existence of the `bundle` in the consensus chain
opaque_bundle_with_proof
.verify::<CBlock>(*domain_id, consensus_block_header.state_root())?;
opaque_bundle_with_proof.verify::<CBlock>(
*domain_id,
consensus_block_header_incl_bundle.state_root(),
)?;
let OpaqueBundleWithProof { bundle, .. } = opaque_bundle_with_proof;

// Bundle with tx out of range, should be either part of invalid bundle with different invalid bundle type
Expand All @@ -138,8 +145,10 @@ where
//
// 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 = runtime_code_with_proof
.verify::<CBlock>(*domain_id, parent_consensus_block_header.state_root())?;
let domain_runtime_code = runtime_code_with_proof.verify::<CBlock>(
*domain_id,
parent_consensus_block_header_incl_bundle.state_root(),
)?;

let runtime_api_light =
RuntimeApiLight::new(self.executor.clone(), domain_runtime_code.into());
Expand All @@ -159,7 +168,7 @@ where
let tx_range = self
.consensus_client
.runtime_api()
.domain_tx_range(*consensus_block_hash, *domain_id)?;
.domain_tx_range(*consensus_block_hash_incl_bundle, *domain_id)?;

let bundle_vrf_hash = U256::from_be_bytes(
bundle.sealed_header.header.proof_of_election.vrf_hash(),
Expand Down Expand Up @@ -214,7 +223,7 @@ where
Client: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
Client::Api: DomainsApi<CBlock, NumberFor<DomainBlock>, <DomainBlock as BlockT>::Hash>
+ ExecutionReceiptApi<CBlock, NumberFor<DomainBlock>, <DomainBlock as BlockT>::Hash>,
Exec: CodeExecutor + 'static,
Exec: CodeExecutor + 'static + RuntimeVersionOf,
{
fn verify_invalid_bundle_proof(
&self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use domain_block_preprocessor::runtime_api_light::RuntimeApiLight;
use domain_runtime_primitives::opaque::Block;
use domain_runtime_primitives::{DomainCoreApi, Hash};
use sc_client_api::StorageProof;
use sc_executor::RuntimeVersionOf;
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_core::traits::CodeExecutor;
Expand Down Expand Up @@ -52,7 +53,7 @@ fn create_runtime_api_light<Exec>(
extrinsic: OpaqueExtrinsic,
) -> Result<RuntimeApiLight<Exec>, VerificationError>
where
Exec: CodeExecutor,
Exec: CodeExecutor + RuntimeVersionOf,
{
let mut runtime_api_light = RuntimeApiLight::new(executor, wasm_bundle);

Expand Down Expand Up @@ -107,7 +108,7 @@ where
CClient: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
CClient::Api: DomainsApi<CBlock, domain_runtime_primitives::BlockNumber, Hash>,
VerifierClient: VerifierApi,
Exec: CodeExecutor + 'static,
Exec: CodeExecutor + 'static + RuntimeVersionOf,
{
/// Constructs a new instance of [`InvalidTransactionProofVerifier`].
pub fn new(
Expand Down Expand Up @@ -224,7 +225,7 @@ where
Client: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + Send + Sync,
Client::Api: DomainsApi<CBlock, domain_runtime_primitives::BlockNumber, Hash>,
VerifierClient: VerifierApi,
Exec: CodeExecutor + 'static,
Exec: CodeExecutor + 'static + RuntimeVersionOf,
{
fn verify_invalid_transaction_proof(
&self,
Expand Down
45 changes: 21 additions & 24 deletions domains/client/block-preprocessor/src/runtime_api_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::runtime_api::{
};
use codec::{Codec, Encode};
use domain_runtime_primitives::{DomainCoreApi, InherentExtrinsicApi};
use sc_executor_common::runtime_blob::RuntimeBlob;
use sc_executor::RuntimeVersionOf;
use sp_api::{ApiError, BlockT, Core, Hasher, RuntimeVersion};
use sp_core::traits::{CallContext, CodeExecutor, FetchRuntimeCode, RuntimeCode};
use sp_messenger::MessengerApi;
Expand Down Expand Up @@ -32,7 +32,7 @@ pub struct RuntimeApiLight<Executor> {
impl<Block, Executor> Core<Block> for RuntimeApiLight<Executor>
where
Block: BlockT,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn __runtime_api_internal_call_api_at(
&self,
Expand All @@ -47,7 +47,7 @@ where
impl<Block, Executor> DomainCoreApi<Block> for RuntimeApiLight<Executor>
where
Block: BlockT,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn __runtime_api_internal_call_api_at(
&self,
Expand All @@ -63,7 +63,7 @@ impl<Block, Executor> MessengerApi<Block, NumberFor<Block>> for RuntimeApiLight<
where
Block: BlockT,
NumberFor<Block>: Codec,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn __runtime_api_internal_call_api_at(
&self,
Expand All @@ -83,7 +83,7 @@ impl<Executor> FetchRuntimeCode for RuntimeApiLight<Executor> {

impl<Executor> RuntimeApiLight<Executor>
where
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
/// Create a new instance of [`RuntimeApiLight`] with empty storage.
pub fn new(executor: Arc<Executor>, runtime_code: Cow<'static, [u8]>) -> Self {
Expand All @@ -110,29 +110,26 @@ where
}
}

fn runtime_version(&self) -> Result<RuntimeVersion, ApiError> {
let runtime_blob = RuntimeBlob::new(&self.runtime_code).map_err(|err| {
ApiError::Application(Box::from(format!("invalid runtime code: {err}")))
})?;
sc_executor::read_embedded_version(&runtime_blob)
.map_err(|err| ApiError::Application(Box::new(err)))?
.ok_or(ApiError::Application(Box::from(
"domain runtime version not found".to_string(),
)))
}

fn dispatch_call(
&self,
fn_name: &dyn Fn(RuntimeVersion) -> &'static str,
input: Vec<u8>,
) -> Result<Vec<u8>, ApiError> {
let runtime_version = self.runtime_version()?;
let fn_name = fn_name(runtime_version);
let mut ext = BasicExternalities::new(self.storage.clone());
let runtime_code = self.runtime_code();
let runtime_version = self
.executor
.runtime_version(&mut ext, &runtime_code)
.map_err(|err| {
ApiError::Application(Box::from(format!(
"failed to read domain runtime version: {err}"
)))
})?;
let fn_name = fn_name(runtime_version);
self.executor
.call(
&mut ext,
&self.runtime_code(),
&runtime_code,
fn_name,
&input,
false,
Expand All @@ -148,7 +145,7 @@ where
impl<Block, Executor> StateRootExtractor<Block> for RuntimeApiLight<Executor>
where
Block: BlockT,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn extract_state_roots(
&self,
Expand All @@ -167,7 +164,7 @@ where
impl<Executor, Block> InherentExtrinsicApi<Block> for RuntimeApiLight<Executor>
where
Block: BlockT,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn __runtime_api_internal_call_api_at(
&self,
Expand All @@ -182,7 +179,7 @@ where
impl<Executor, Block> SignerExtractor<Block> for RuntimeApiLight<Executor>
where
Block: BlockT,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn extract_signer(
&self,
Expand All @@ -196,7 +193,7 @@ where
impl<Executor, Block> SetCodeConstructor<Block> for RuntimeApiLight<Executor>
where
Block: BlockT,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn construct_set_code_extrinsic(
&self,
Expand All @@ -210,7 +207,7 @@ where
impl<Executor, Block> InherentExtrinsicConstructor<Block> for RuntimeApiLight<Executor>
where
Block: BlockT,
Executor: CodeExecutor,
Executor: CodeExecutor + RuntimeVersionOf,
{
fn construct_timestamp_inherent_extrinsic(
&self,
Expand Down
2 changes: 1 addition & 1 deletion domains/client/domain-operator/src/aux_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ pub(super) fn target_receipt_is_pruned(

// TODO: Naming could use some work
#[derive(Encode, Decode, Debug, PartialEq)]
pub(super) enum InvalidBundlesMismatchType {
pub enum InvalidBundlesMismatchType {
ValidAsInvalid,
InvalidAsValid,
}
Expand Down
5 changes: 4 additions & 1 deletion domains/client/domain-operator/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,15 @@ where
bundle_index,
)?;

let consensus_block_hash_incl_er = self.consensus_client.info().best_hash;

Ok(FraudProof::InvalidBundles(
InvalidBundlesFraudProof::MissingInvalidBundleEntry(
MissingInvalidBundleEntryFraudProof::new(
domain_id,
bad_receipt_hash,
consensus_block_hash.clone(),
consensus_block_hash_incl_er,
bundle_index,
bundle_with_proof,
runtime_code_with_proof,
Expand All @@ -248,7 +251,7 @@ where
))
}

pub(crate) fn generate_invalid_bundle_field_proof(
pub fn generate_invalid_bundle_field_proof(
&self,
domain_id: DomainId,
local_receipt: &ExecutionReceiptFor<Block, CBlock>,
Expand Down
4 changes: 2 additions & 2 deletions domains/client/domain-operator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
#![feature(const_option)]
#![feature(extract_if)]

mod aux_schema;
pub mod aux_schema;
mod bootstrapper;
mod bundle_processor;
mod bundle_producer_election_solver;
Expand All @@ -70,7 +70,7 @@ pub mod domain_bundle_producer;
mod domain_bundle_proposer;
mod domain_worker;
mod domain_worker_starter;
mod fraud_proof;
pub mod fraud_proof;
mod operator;
mod parent_chain;
#[cfg(test)]
Expand Down