Skip to content

Commit

Permalink
Merge pull request #2168 from subspace/inherent_fraud_proof
Browse files Browse the repository at this point in the history
Fraud proof: Invalid Bundle, `true` and `false`, for inherent extrinsic
  • Loading branch information
vedhavyas authored Oct 30, 2023
2 parents d3b8313 + 2ecb9c8 commit d9aba72
Show file tree
Hide file tree
Showing 11 changed files with 651 additions and 144 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,7 @@ mod pallet {
match call {
Call::submit_bundle { opaque_bundle } => {
if let Err(e) = Self::validate_bundle(opaque_bundle) {
log::debug!(
log::error!(
target: "runtime::domains",
"Bad bundle {:?}, error: {e:?}", opaque_bundle.domain_id(),
);
Expand All @@ -1309,7 +1309,7 @@ mod pallet {
}
Call::submit_fraud_proof { fraud_proof } => {
if let Err(e) = Self::validate_fraud_proof(fraud_proof) {
log::debug!(
log::error!(
target: "runtime::domains",
"Bad fraud proof {:?}, error: {e:?}", fraud_proof.domain_id(),
);
Expand Down
164 changes: 158 additions & 6 deletions crates/pallet-domains/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,16 @@ use sp_core::crypto::Pair;
use sp_core::storage::{StateVersion, StorageKey};
use sp_core::{Get, H256, U256};
use sp_domains::merkle_tree::MerkleTree;
use sp_domains::proof_provider_and_verifier::StorageProofProvider;
use sp_domains::storage::RawGenesis;
use sp_domains::{
BundleHeader, DomainId, DomainsHoldIdentifier, ExecutionReceipt, InboxedBundle, OpaqueBundle,
OperatorAllowList, OperatorId, OperatorPair, ProofOfElection, RuntimeType, SealedBundleHeader,
StakingHoldIdentifier,
BundleHeader, DomainId, DomainsHoldIdentifier, ExecutionReceipt, InboxedBundle,
InvalidBundleType, OpaqueBundle, OperatorAllowList, OperatorId, OperatorPair, ProofOfElection,
RuntimeType, SealedBundleHeader, StakingHoldIdentifier,
};
use sp_domains_fraud_proof::fraud_proof::{
ExtrinsicDigest, FraudProof, InvalidDomainBlockHashProof, InvalidExtrinsicsRootProof,
InvalidTotalRewardsProof, ValidBundleDigest,
ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, InvalidDomainBlockHashProof,
InvalidExtrinsicsRootProof, InvalidTotalRewardsProof, ValidBundleDigest,
};
use sp_domains_fraud_proof::{
FraudProofExtension, FraudProofHostFunctions, FraudProofVerificationInfoRequest,
Expand All @@ -39,7 +40,7 @@ use sp_state_machine::backend::AsTrieBackend;
use sp_state_machine::{prove_read, Backend, TrieBackendBuilder};
use sp_std::sync::Arc;
use sp_trie::trie_types::TrieDBMutBuilderV1;
use sp_trie::{PrefixedMemoryDB, StorageProof, TrieMut};
use sp_trie::{LayoutV1, PrefixedMemoryDB, StorageProof, TrieMut};
use sp_version::RuntimeVersion;
use std::sync::atomic::{AtomicU64, Ordering};
use subspace_core_primitives::{Randomness, U256 as P256};
Expand Down Expand Up @@ -256,6 +257,7 @@ pub(crate) struct MockDomainFraudProofExtension {
timestamp: Moment,
runtime_code: Vec<u8>,
tx_range: bool,
is_inherent: bool,
}

impl FraudProofHostFunctions for MockDomainFraudProofExtension {
Expand Down Expand Up @@ -301,6 +303,9 @@ impl FraudProofHostFunctions for MockDomainFraudProofExtension {
FraudProofVerificationInfoRequest::TxRangeCheck { .. } => {
FraudProofVerificationInfoResponse::TxRangeCheck(self.tx_range)
}
FraudProofVerificationInfoRequest::InherentExtrinsicCheck { .. } => {
FraudProofVerificationInfoResponse::InherentExtrinsicCheck(self.is_inherent)
}
};

Some(response)
Expand Down Expand Up @@ -968,6 +973,7 @@ fn test_invalid_domain_extrinsic_root_proof() {
timestamp: 1000,
runtime_code: vec![1, 2, 3, 4],
tx_range: true,
is_inherent: true,
}));
ext.register_extension(fraud_proof_ext);

Expand All @@ -992,6 +998,152 @@ fn generate_invalid_domain_extrinsic_root_fraud_proof<T: Config + pallet_timesta
})
}

#[test]
fn test_true_invalid_bundles_inherent_extrinsic_proof() {
let creator = 0u64;
let operator_id = 1u64;
let head_domain_number = 10;
let mut ext = new_test_ext_with_extensions();
let fraud_proof = ext.execute_with(|| {
let domain_id = register_genesis_domain(creator, vec![operator_id]);
extend_block_tree(domain_id, operator_id, head_domain_number + 1);
assert_eq!(
HeadReceiptNumber::<Test>::get(domain_id),
head_domain_number
);

let inherent_extrinsic = vec![1, 2, 3].encode();
let extrinsics = vec![inherent_extrinsic];
let bundle_extrinsic_root =
BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1);

let bad_receipt_at = 8;
let mut domain_block = get_block_tree_node_at::<Test>(domain_id, bad_receipt_at).unwrap();
let bad_receipt = &mut domain_block.execution_receipt;
// bad receipt marks this particular bundle as valid even though bundle contains inherent extrinsic
bad_receipt.inboxed_bundles =
vec![InboxedBundle::valid(H256::random(), bundle_extrinsic_root)];
bad_receipt.domain_block_extrinsic_root = H256::random();

let bad_receipt_hash = bad_receipt.hash::<DomainHashingFor<Test>>();
let fraud_proof = generate_invalid_bundle_inherent_extrinsic_fraud_proof::<Test>(
domain_id,
bad_receipt_hash,
0,
0,
extrinsics,
true,
);
let (consensus_block_number, consensus_block_hash) = (
bad_receipt.consensus_block_number,
bad_receipt.consensus_block_hash,
);
ConsensusBlockHash::<Test>::insert(domain_id, consensus_block_number, consensus_block_hash);
BlockTreeNodes::<Test>::insert(bad_receipt_hash, domain_block);
fraud_proof
});

let fraud_proof_ext = FraudProofExtension::new(Arc::new(MockDomainFraudProofExtension {
block_randomness: Randomness::from([1u8; 32]),
timestamp: 1000,
runtime_code: vec![1, 2, 3, 4],
tx_range: true,
// return `true` indicating this is an inherent extrinsic
is_inherent: true,
}));
ext.register_extension(fraud_proof_ext);

ext.execute_with(|| {
assert_ok!(Domains::validate_fraud_proof(&fraud_proof),);
})
}

#[test]
fn test_false_invalid_bundles_inherent_extrinsic_proof() {
let creator = 0u64;
let operator_id = 1u64;
let head_domain_number = 10;
let mut ext = new_test_ext_with_extensions();
let fraud_proof = ext.execute_with(|| {
let domain_id = register_genesis_domain(creator, vec![operator_id]);
extend_block_tree(domain_id, operator_id, head_domain_number + 1);
assert_eq!(
HeadReceiptNumber::<Test>::get(domain_id),
head_domain_number
);

let non_inherent_extrinsic = vec![1, 2, 3].encode();
let extrinsics = vec![non_inherent_extrinsic];
let bundle_extrinsic_root =
BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1);

let bad_receipt_at = 8;
let mut domain_block = get_block_tree_node_at::<Test>(domain_id, bad_receipt_at).unwrap();
let bad_receipt = &mut domain_block.execution_receipt;
// bad receipt marks this bundle as invalid even though bundle do not contain inherent extrinsic.
bad_receipt.inboxed_bundles = vec![InboxedBundle::invalid(
InvalidBundleType::InherentExtrinsic(0),
bundle_extrinsic_root,
)];
bad_receipt.domain_block_extrinsic_root = H256::random();

let bad_receipt_hash = bad_receipt.hash::<DomainHashingFor<Test>>();
let fraud_proof = generate_invalid_bundle_inherent_extrinsic_fraud_proof::<Test>(
domain_id,
bad_receipt_hash,
0,
0,
extrinsics,
false,
);
let (consensus_block_number, consensus_block_hash) = (
bad_receipt.consensus_block_number,
bad_receipt.consensus_block_hash,
);
ConsensusBlockHash::<Test>::insert(domain_id, consensus_block_number, consensus_block_hash);
BlockTreeNodes::<Test>::insert(bad_receipt_hash, domain_block);
fraud_proof
});

let fraud_proof_ext = FraudProofExtension::new(Arc::new(MockDomainFraudProofExtension {
block_randomness: Randomness::from([1u8; 32]),
timestamp: 1000,
runtime_code: vec![1, 2, 3, 4],
tx_range: true,
// return `false` indicating this is not an inherent extrinsic
is_inherent: false,
}));
ext.register_extension(fraud_proof_ext);

ext.execute_with(|| {
assert_ok!(Domains::validate_fraud_proof(&fraud_proof),);
})
}

fn generate_invalid_bundle_inherent_extrinsic_fraud_proof<T: Config>(
domain_id: DomainId,
bad_receipt_hash: ReceiptHashFor<T>,
bundle_index: u32,
bundle_extrinsic_index: u32,
bundle_extrinsics: Vec<Vec<u8>>,
is_true_invalid_fraud_proof: bool,
) -> FraudProof<BlockNumberFor<T>, T::Hash, T::DomainHeader> {
let extrinsic_inclusion_proof =
StorageProofProvider::<LayoutV1<BlakeTwo256>>::generate_enumerated_proof_of_inclusion(
bundle_extrinsics.as_slice(),
bundle_extrinsic_index,
)
.unwrap();
FraudProof::InvalidBundles(InvalidBundlesFraudProof {
domain_id,
bad_receipt_hash,
bundle_index,
invalid_bundle_type: InvalidBundleType::InherentExtrinsic(bundle_extrinsic_index),
extrinsic_inclusion_proof,
is_true_invalid_fraud_proof,
})
}

#[test]
fn test_invalid_domain_block_hash_fraud_proof() {
let creator = 0u64;
Expand Down
41 changes: 16 additions & 25 deletions crates/sp-domains-fraud-proof/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ impl ExecutionPhase {
pub fn decode_execution_result<Header: HeaderT>(
&self,
execution_result: Vec<u8>,
) -> Result<Header::Hash, VerificationError> {
) -> Result<Header::Hash, VerificationError<Header::Hash>> {
match self {
Self::InitializeBlock | Self::ApplyExtrinsic { .. } => {
let encoded_storage_root = Vec::<u8>::decode(&mut execution_result.as_slice())
Expand All @@ -88,7 +88,7 @@ impl ExecutionPhase {
&self,
bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
) -> Result<(H256, H256), VerificationError>
) -> Result<(H256, H256), VerificationError<DomainHeader::Hash>>
where
CBlock: BlockT,
DomainHeader: HeaderT,
Expand Down Expand Up @@ -128,7 +128,7 @@ impl ExecutionPhase {
&self,
bad_receipt: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
bad_receipt_parent: &ExecutionReceiptFor<DomainHeader, CBlock, Balance>,
) -> Result<Vec<u8>, VerificationError>
) -> Result<Vec<u8>, VerificationError<DomainHeader::Hash>>
where
CBlock: BlockT,
DomainHeader: HeaderT,
Expand Down Expand Up @@ -177,7 +177,7 @@ impl ExecutionPhase {
/// Error type of fraud proof verification on consensus node.
#[derive(Debug)]
#[cfg_attr(feature = "thiserror", derive(thiserror::Error))]
pub enum VerificationError {
pub enum VerificationError<DomainHash> {
/// Hash of the consensus block being challenged not found.
#[cfg_attr(feature = "thiserror", error("consensus block hash not found"))]
ConsensusBlockHashNotFound,
Expand Down Expand Up @@ -301,20 +301,14 @@ pub enum VerificationError {
UnexpectedTargetedBundleEntry {
bundle_index: u32,
fraud_proof_invalid_type_of_proof: InvalidBundleType,
targeted_entry_bundle: BundleValidity<H256>,
targeted_entry_bundle: BundleValidity<DomainHash>,
},
/// Tx range host function did not return response (returned None)
#[cfg_attr(
feature = "thiserror",
error("Tx range host function did not return a response (returned None)")
)]
FailedToGetResponseFromTxRangeHostFn,
/// Unable to receive tx range from host function
#[cfg_attr(
feature = "thiserror",
error("Received invalid information from tx range host function")
)]
ReceivedInvalidInfoFromTxRangeHostFn,
/// Failed to get the bundle body
#[cfg_attr(feature = "thiserror", error("Failed to get the bundle body"))]
FailedToGetDomainBundleBody,
Expand All @@ -327,43 +321,40 @@ pub enum VerificationError {
error("The target valid bundle not found from the target bad receipt")
)]
TargetValidBundleNotFound,
}

/// Proof data specific to each *expected* invalid bundle type
#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub enum ProofDataPerInvalidBundleType {
OutOfRangeTx,
/// Failed to check if a given extrinsic is inherent or not.
#[cfg_attr(
feature = "thiserror",
error("Failed to check if a given extrinsic is inherent or not")
)]
FailedToCheckInherentExtrinsic,
}

#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub struct InvalidBundlesFraudProof<ReceiptHash> {
pub bad_receipt_hash: ReceiptHash,
pub domain_id: DomainId,
pub bundle_index: u32,
pub mismatched_extrinsic_index: u32,
pub extrinsic_inclusion_proof: Vec<Vec<u8>>,
pub invalid_bundle_type: InvalidBundleType,
pub extrinsic_inclusion_proof: StorageProof,
pub is_true_invalid_fraud_proof: bool,
pub proof_data: ProofDataPerInvalidBundleType,
}

impl<ReceiptHash> InvalidBundlesFraudProof<ReceiptHash> {
pub fn new(
bad_receipt_hash: ReceiptHash,
domain_id: DomainId,
bundle_index: u32,
mismatched_extrinsic_index: u32,
extrinsic_inclusion_proof: Vec<Vec<u8>>,
invalid_bundle_type: InvalidBundleType,
extrinsic_inclusion_proof: StorageProof,
is_true_invalid_fraud_proof: bool,
proof_data: ProofDataPerInvalidBundleType,
) -> Self {
Self {
bad_receipt_hash,
domain_id,
bundle_index,
mismatched_extrinsic_index,
invalid_bundle_type,
extrinsic_inclusion_proof,
is_true_invalid_fraud_proof,
proof_data,
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions crates/sp-domains-fraud-proof/src/host_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,30 @@ where
&domain_tx_range,
).ok()
}

fn is_inherent_extrinsic(
&self,
consensus_block_hash: H256,
domain_id: DomainId,
opaque_extrinsic: OpaqueExtrinsic,
) -> Option<bool> {
let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?;

let domain_runtime_api_light =
RuntimeApiLight::new(self.executor.clone(), runtime_code.into());

let encoded_extrinsic = opaque_extrinsic.encode();
let extrinsic =
<DomainBlock as BlockT>::Extrinsic::decode(&mut encoded_extrinsic.as_slice()).ok()?;

<RuntimeApiLight<Executor> as domain_runtime_primitives::DomainCoreApi<
DomainBlock,
>>::is_inherent_extrinsic(
&domain_runtime_api_light,
Default::default(), // Doesn't matter for RuntimeApiLight
&extrinsic
).ok()
}
}

impl<Block, Client, DomainBlock, Executor> FraudProofHostFunctions
Expand Down Expand Up @@ -318,6 +342,14 @@ where
.map(|is_tx_in_range| {
FraudProofVerificationInfoResponse::TxRangeCheck(is_tx_in_range)
}),
FraudProofVerificationInfoRequest::InherentExtrinsicCheck {
domain_id,
opaque_extrinsic,
} => self
.is_inherent_extrinsic(consensus_block_hash, domain_id, opaque_extrinsic)
.map(|is_inherent| {
FraudProofVerificationInfoResponse::InherentExtrinsicCheck(is_inherent)
}),
}
}

Expand Down
Loading

0 comments on commit d9aba72

Please sign in to comment.