diff --git a/Cargo.lock b/Cargo.lock index 5f1f02f3c8..ec68787172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10843,6 +10843,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rs_merkle", + "sc-client-api", "scale-info", "serde", "sp-api", @@ -11602,6 +11603,7 @@ version = "0.1.0" dependencies = [ "domain-block-builder", "domain-block-preprocessor", + "domain-client-operator", "domain-runtime-primitives", "domain-test-service", "frame-support", @@ -11612,6 +11614,7 @@ dependencies = [ "parity-scale-codec", "sc-cli", "sc-client-api", + "sc-executor", "sc-service", "sp-api", "sp-blockchain", @@ -11623,6 +11626,7 @@ dependencies = [ "sp-runtime", "sp-state-machine", "sp-trie", + "subspace-core-primitives", "subspace-runtime-primitives", "subspace-test-client", "subspace-test-runtime", @@ -11937,6 +11941,7 @@ dependencies = [ name = "subspace-test-client" version = "0.1.0" dependencies = [ + "domain-runtime-primitives", "evm-domain-test-runtime", "fp-evm", "futures", @@ -11973,6 +11978,7 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "hex-literal", + "log", "orml-vesting", "pallet-balances", "pallet-domains", diff --git a/crates/pallet-domains/src/domain_registry.rs b/crates/pallet-domains/src/domain_registry.rs index cb3481a368..b6f8a98a72 100644 --- a/crates/pallet-domains/src/domain_registry.rs +++ b/crates/pallet-domains/src/domain_registry.rs @@ -4,7 +4,8 @@ use crate::block_tree::import_genesis_receipt; use crate::pallet::DomainStakingSummary; use crate::staking::StakingSummary; use crate::{ - Config, DomainRegistry, ExecutionReceiptOf, HoldIdentifier, NextDomainId, RuntimeRegistry, + Config, DomainRegistry, DomainRuntimeMap, ExecutionReceiptOf, HoldIdentifier, NextDomainId, + RuntimeRegistry, }; use codec::{Decode, Encode}; use frame_support::traits::fungible::{Inspect, MutateHold}; @@ -125,9 +126,10 @@ pub(crate) fn do_instantiate_domain( can_instantiate_domain::(&owner_account_id, &domain_config)?; let domain_id = NextDomainId::::get(); + let runtime_id = domain_config.runtime_id; let genesis_receipt = { - let runtime_obj = RuntimeRegistry::::get(domain_config.runtime_id) + let runtime_obj = RuntimeRegistry::::get(runtime_id) .expect("Runtime object must exist as checked in `can_instantiate_domain`; qed"); initialize_genesis_receipt::( domain_id, @@ -146,6 +148,7 @@ pub(crate) fn do_instantiate_domain( raw_genesis_config, }; DomainRegistry::::insert(domain_id, domain_obj); + DomainRuntimeMap::::insert(domain_id, runtime_id); let next_domain_id = domain_id.checked_add(&1.into()).ok_or(Error::MaxDomainId)?; NextDomainId::::set(next_domain_id); @@ -208,10 +211,9 @@ fn initialize_genesis_receipt( mod tests { use super::*; use crate::pallet::{DomainRegistry, NextDomainId, RuntimeRegistry}; - use crate::runtime_registry::RuntimeObject; use crate::tests::{new_test_ext, GenesisStateRootGenerater, Test}; use frame_support::traits::Currency; - use sp_domains::GenesisReceiptExtension; + use sp_domains::{GenesisReceiptExtension, RuntimeObject}; use sp_std::vec; use sp_version::RuntimeVersion; use std::sync::Arc; diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 4f754091fc..6e5880fa40 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -49,8 +49,8 @@ use sp_domains::verification::{ }; use sp_domains::{ DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId, - OperatorPublicKey, ProofOfElection, RuntimeId, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, - EMPTY_EXTRINSIC_ROOT, + OperatorPublicKey, ProofOfElection, ReceiptHash, RuntimeId, + DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, EMPTY_EXTRINSIC_ROOT, }; use sp_runtime::traits::{BlakeTwo256, CheckedSub, Hash, One, Zero}; use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating}; @@ -90,6 +90,13 @@ pub type OpaqueBundleOf = OpaqueBundle< BalanceOf, >; +pub type FraudProofOf = FraudProof< + BlockNumberFor, + ::Hash, + ::DomainNumber, + ::DomainHash, +>; + /// Parameters used to verify proof of election. #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] pub(crate) struct ElectionVerificationParams { @@ -110,8 +117,7 @@ mod pallet { }; use crate::runtime_registry::{ do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes, - register_runtime_at_genesis, Error as RuntimeRegistryError, RuntimeObject, - ScheduledRuntimeUpgrade, + register_runtime_at_genesis, Error as RuntimeRegistryError, ScheduledRuntimeUpgrade, }; #[cfg(not(feature = "runtime-benchmarks"))] use crate::staking::do_reward_operators; @@ -128,7 +134,8 @@ mod pallet { }; use crate::weights::WeightInfo; use crate::{ - BalanceOf, ElectionVerificationParams, HoldIdentifier, NominatorId, OpaqueBundleOf, + BalanceOf, ElectionVerificationParams, FraudProofOf, HoldIdentifier, NominatorId, + OpaqueBundleOf, }; use codec::FullCodec; use frame_support::pallet_prelude::*; @@ -138,12 +145,12 @@ mod pallet { use frame_support::{Identity, PalletError}; use frame_system::pallet_prelude::*; use sp_core::H256; - use sp_domains::fraud_proof::{DeriveExtrinsics, FraudProof, StorageKeys}; + use sp_domains::fraud_proof::{DeriveExtrinsics, StorageKeys}; use sp_domains::inherents::{InherentError, InherentType, INHERENT_IDENTIFIER}; use sp_domains::transaction::InvalidTransactionCode; use sp_domains::{ BundleDigest, DomainId, EpochIndex, GenesisDomain, OperatorId, ReceiptHash, RuntimeId, - RuntimeType, + RuntimeObject, RuntimeType, }; use sp_runtime::traits::{ AtLeast32BitUnsigned, BlockNumberProvider, Bounded, CheckEqual, CheckedAdd, MaybeDisplay, @@ -446,6 +453,11 @@ mod pallet { OptionQuery, >; + /// A handy mapping of `domain_id` -> `runtime_id`, used in fraud proof to generate storage + /// proof of the domain runtime code. + #[pallet::storage] + pub(super) type DomainRuntimeMap = StorageMap<_, Identity, DomainId, RuntimeId, OptionQuery>; + /// The domain block tree, map (`domain_id`, `domain_block_number`) to the hash of a domain blocks, /// which can be used get the domain block in `DomainBlocks` #[pallet::storage] @@ -863,7 +875,7 @@ mod pallet { #[pallet::weight((Weight::from_all(10_000), Pays::No))] pub fn submit_fraud_proof( origin: OriginFor, - fraud_proof: Box, T::Hash>>, + fraud_proof: Box>, ) -> DispatchResult { ensure_none(origin)?; @@ -1395,8 +1407,7 @@ impl Pallet { } pub fn runtime_id(domain_id: DomainId) -> Option { - DomainRegistry::::get(domain_id) - .map(|domain_object| domain_object.domain_config.runtime_id) + DomainRuntimeMap::::get(domain_id) } pub fn domain_instance_data( @@ -1569,9 +1580,7 @@ impl Pallet { Ok(()) } - fn validate_fraud_proof( - fraud_proof: &FraudProof, T::Hash>, - ) -> Result<(), FraudProofError> { + fn validate_fraud_proof(fraud_proof: &FraudProofOf) -> Result<(), FraudProofError> { let bad_receipt = DomainBlocks::::get(fraud_proof.bad_receipt_hash()) .ok_or(FraudProofError::BadReceiptNotFound)? .execution_receipt; @@ -1762,6 +1771,10 @@ impl Pallet { }); } + pub fn execution_receipt_by_hash(hash: ReceiptHash) -> Option> { + DomainBlocks::::get(hash).map(|domain_block| domain_block.execution_receipt) + } + /// Returns if there are any ERs in the challenge period that have non empty extrinsics. /// Note that Genesis ER is also considered special and hence non empty pub fn non_empty_er_exists(domain_id: DomainId) -> bool { @@ -1821,7 +1834,7 @@ where } /// Submits an unsigned extrinsic [`Call::submit_fraud_proof`]. - pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProof, T::Hash>) { + pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofOf) { let call = Call::submit_fraud_proof { fraud_proof: Box::new(fraud_proof), }; diff --git a/crates/pallet-domains/src/runtime_registry.rs b/crates/pallet-domains/src/runtime_registry.rs index 8e5dbb2bc7..0075ac42b3 100644 --- a/crates/pallet-domains/src/runtime_registry.rs +++ b/crates/pallet-domains/src/runtime_registry.rs @@ -7,7 +7,7 @@ use frame_support::PalletError; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; use sp_core::Hasher; -use sp_domains::{DomainsDigestItem, RuntimeId, RuntimeType}; +use sp_domains::{DomainsDigestItem, RuntimeId, RuntimeObject, RuntimeType}; use sp_runtime::traits::{CheckedAdd, Get}; use sp_runtime::DigestItem; use sp_std::vec::Vec; @@ -25,18 +25,6 @@ pub enum Error { MaxScheduledBlockNumber, } -#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] -pub struct RuntimeObject { - pub runtime_name: Vec, - pub runtime_type: RuntimeType, - pub runtime_upgrades: u32, - pub hash: Hash, - pub code: Vec, - pub version: RuntimeVersion, - pub created_at: Number, - pub updated_at: Number, -} - #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] pub struct ScheduledRuntimeUpgrade { pub code: Vec, diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 58d8cd8f13..a225fb71c4 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -3,7 +3,8 @@ use crate::domain_registry::{DomainConfig, DomainObject}; use crate::{ self as pallet_domains, BalanceOf, BlockTree, BundleError, Config, ConsensusBlockInfo, DomainBlocks, DomainRegistry, ExecutionInbox, ExecutionReceiptOf, FraudProofError, - FungibleHoldId, HeadReceiptNumber, NextDomainId, Operator, OperatorStatus, Operators, + FraudProofOf, FungibleHoldId, HeadReceiptNumber, NextDomainId, Operator, OperatorStatus, + Operators, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::dispatch::RawOrigin; @@ -827,7 +828,7 @@ fn generate_invalid_total_rewards_fraud_proof( domain_id: DomainId, bad_receipt_hash: ReceiptHash, rewards: BalanceOf, -) -> (FraudProof, T::Hash>, T::Hash) { +) -> (FraudProofOf, T::Hash) { let storage_key = sp_domains::fraud_proof::operator_block_rewards_final_key(); let mut root = T::Hash::default(); let mut mdb = PrefixedMemoryDB::::default(); @@ -901,7 +902,7 @@ fn generate_invalid_domain_extrinsic_root_fraud_proof (FraudProof, T::Hash>, T::Hash) { +) -> (FraudProofOf, T::Hash) { let randomness_storage_key = frame_support::storage::storage_prefix("Subspace".as_ref(), "BlockRandomness".as_ref()) .to_vec(); diff --git a/crates/sc-consensus-fraud-proof/src/lib.rs b/crates/sc-consensus-fraud-proof/src/lib.rs index 9baea3d40b..82f7f20433 100644 --- a/crates/sc-consensus-fraud-proof/src/lib.rs +++ b/crates/sc-consensus-fraud-proof/src/lib.rs @@ -16,12 +16,11 @@ //! Subspace fraud proof verification in the consensus level. -use codec::{Decode, Encode}; use sc_consensus::block_import::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; use sp_api::ProvideRuntimeApi; use sp_consensus::Error as ConsensusError; use sp_domains::DomainsApi; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::marker::PhantomData; use std::sync::Arc; use subspace_fraud_proof::VerifyFraudProof; @@ -33,15 +32,15 @@ use subspace_fraud_proof::VerifyFraudProof; /// /// This block import object should be used with the subspace consensus block import together until /// the fraud proof verification can be done in the runtime properly. -pub struct FraudProofBlockImport { +pub struct FraudProofBlockImport { inner: I, client: Arc, fraud_proof_verifier: Verifier, - _phantom: PhantomData<(Block, DomainNumber, DomainHash)>, + _phantom: PhantomData<(Block, DomainBlock)>, } -impl Clone - for FraudProofBlockImport +impl Clone + for FraudProofBlockImport where I: Clone, Verifier: Clone, @@ -57,16 +56,15 @@ where } #[async_trait::async_trait] -impl BlockImport - for FraudProofBlockImport +impl BlockImport + for FraudProofBlockImport where Block: BlockT, + DomainBlock: BlockT, Client: ProvideRuntimeApi + Send + Sync + 'static, - Client::Api: DomainsApi, + Client::Api: DomainsApi, DomainBlock::Hash>, Inner: BlockImport + Send + Sync, - Verifier: VerifyFraudProof + Send + Sync, - DomainNumber: Encode + Decode + Send + Sync, - DomainHash: Encode + Decode + Send + Sync, + Verifier: VerifyFraudProof + Send + Sync, { type Error = ConsensusError; @@ -110,15 +108,15 @@ where } } -pub fn block_import( +pub fn block_import( client: Arc, wrapped_block_import: I, fraud_proof_verifier: Verifier, -) -> FraudProofBlockImport { +) -> FraudProofBlockImport { FraudProofBlockImport { inner: wrapped_block_import, client, fraud_proof_verifier, - _phantom: PhantomData::<(Block, DomainNumber, DomainHash)>, + _phantom: PhantomData::<(Block, DomainBlock)>, } } diff --git a/crates/sp-domains/Cargo.toml b/crates/sp-domains/Cargo.toml index 4c30be0676..3e3a994806 100644 --- a/crates/sp-domains/Cargo.toml +++ b/crates/sp-domains/Cargo.toml @@ -23,6 +23,7 @@ rand = { version = "0.8.5", default-features = false } rand_chacha = { version = "0.3.1", default-features = false } rs_merkle = { version = "1.4.1", default-features = false } scale-info = { version = "2.7.0", default-features = false, features = ["derive"] } +sc-client-api = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7", optional = true } serde = { version = "1.0.183", default-features = false, features = ["alloc", "derive"] } sp-api = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-application-crypto = { version = "23.0.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } @@ -60,6 +61,7 @@ std = [ "rand_chacha/std", "rs_merkle/std", "scale-info/std", + "sc-client-api", "serde/std", "sp-api/std", "sp-blockchain", diff --git a/crates/sp-domains/src/fraud_proof.rs b/crates/sp-domains/src/fraud_proof.rs index 7300d62b11..0a4214c673 100644 --- a/crates/sp-domains/src/fraud_proof.rs +++ b/crates/sp-domains/src/fraud_proof.rs @@ -1,11 +1,14 @@ -use crate::{DomainId, ReceiptHash, SealedBundleHeader}; +use crate::storage_proof::{DomainRuntimeCodeWithProof, OpaqueBundleWithProof}; +use crate::{DomainId, ExecutionReceipt, ReceiptHash, SealedBundleHeader}; use hash_db::Hasher; use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_consensus_slots::Slot; use sp_core::storage::StorageKey; use sp_core::H256; -use sp_runtime::traits::{BlakeTwo256, Hash as HashT, Header as HeaderT}; +use sp_runtime::traits::{ + BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT, NumberFor, Zero, +}; use sp_std::vec::Vec; use sp_trie::StorageProof; use subspace_core_primitives::BlockNumber; @@ -164,8 +167,8 @@ pub enum VerificationError { Client(#[from] sp_blockchain::Error), /// Invalid storage proof. #[cfg(feature = "std")] - #[cfg_attr(feature = "thiserror", error("Invalid stroage proof"))] - InvalidStorageProof, + #[cfg_attr(feature = "thiserror", error("Invalid stroage proof: {0:?}"))] + InvalidStorageProof(#[from] crate::storage_proof::VerificationError), /// Can not find signer from the domain extrinsic. #[cfg_attr( feature = "thiserror", @@ -175,6 +178,9 @@ pub enum VerificationError { /// Domain state root not found. #[cfg_attr(feature = "thiserror", error("Domain state root not found"))] DomainStateRootNotFound, + /// Execution receipt not found. + #[cfg_attr(feature = "thiserror", error("Execution receipt not found"))] + ExecutionReceiptNotFound, /// Fail to get runtime code. // The `String` here actually repersenting the `sc_executor_common::error::WasmError` // error, but it will be improper to use `WasmError` directly here since it will make @@ -188,20 +194,66 @@ pub enum VerificationError { error("Oneshot error when verifying fraud proof in tx pool: {0}") )] Oneshot(String), + /// Tx indicated in fraud proof is in range + #[cfg_attr(feature = "thiserror", error("Tx is in range: {extrinsic_index}"))] + TxIsInRange { extrinsic_index: u32 }, + /// Bundle passed in fraud proof is incorrect + #[cfg_attr( + feature = "thiserror", + error("Bundle passed in fraud proof is incorrect: {bundle_index}") + )] + IncorrectBundleInFraudProof { bundle_index: u32 }, } -// TODO: Define rest of the fraud proof fields #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub struct MissingInvalidBundleEntryFraudProof { - domain_id: DomainId, - bundle_index: u32, +pub enum MissingBundleAdditionalData { + OutOfRangeTx { extrinsic_index: u32 }, } -impl MissingInvalidBundleEntryFraudProof { - pub fn new(domain_id: DomainId, bundle_index: u32) -> Self { +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub struct ConsensusBlockDetails { + pub consensus_block_incl_bundle: Hash, + pub consensus_block_incl_er: Hash, +} + +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub struct MissingInvalidBundleEntryFraudProof { + pub domain_id: DomainId, + pub bad_receipt_hash: H256, + pub consensus_block_details: ConsensusBlockDetails, + pub bundle_index: u32, + pub opaque_bundle_with_proof: + OpaqueBundleWithProof, + pub runtime_code_with_proof: DomainRuntimeCodeWithProof, + pub additional_data: MissingBundleAdditionalData, +} + +impl + MissingInvalidBundleEntryFraudProof +{ + pub fn new( + domain_id: DomainId, + bad_receipt_hash: H256, + consensus_block_details: ConsensusBlockDetails, + bundle_index: u32, + opaque_bundle_with_proof: OpaqueBundleWithProof< + Number, + Hash, + DomainNumber, + DomainHash, + Balance, + >, + runtime_code_with_proof: DomainRuntimeCodeWithProof, + additional_data: MissingBundleAdditionalData, + ) -> Self { Self { domain_id, + bad_receipt_hash, + consensus_block_details, bundle_index, + opaque_bundle_with_proof, + runtime_code_with_proof, + additional_data, } } } @@ -224,12 +276,16 @@ impl ValidAsInvalidBundleEntryFraudProof { /// Fraud proof indicating that `invalid_bundles` field of the receipt is incorrect #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub enum InvalidBundlesFraudProof { - MissingInvalidBundleEntry(MissingInvalidBundleEntryFraudProof), +pub enum InvalidBundlesFraudProof { + MissingInvalidBundleEntry( + MissingInvalidBundleEntryFraudProof, + ), ValidAsInvalid(ValidAsInvalidBundleEntryFraudProof), } -impl InvalidBundlesFraudProof { +impl + InvalidBundlesFraudProof +{ pub fn domain_id(&self) -> DomainId { match self { InvalidBundlesFraudProof::MissingInvalidBundleEntry(proof) => proof.domain_id, @@ -242,7 +298,7 @@ impl InvalidBundlesFraudProof { // TODO: Revisit when fraud proof v2 is implemented. #[allow(clippy::large_enum_variant)] #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] -pub enum FraudProof { +pub enum FraudProof { InvalidStateTransition(InvalidStateTransitionProof), InvalidTransaction(InvalidTransactionProof), BundleEquivocation(BundleEquivocationProof), @@ -257,10 +313,16 @@ pub enum FraudProof { /// Hash of the bad receipt this fraud proof targeted bad_receipt_hash: ReceiptHash, }, - InvalidBundles(InvalidBundlesFraudProof), + InvalidBundles(InvalidBundlesFraudProof), } -impl FraudProof { +impl FraudProof +where + Number: Encode + Zero, + Hash: Encode + Default, + DomainNumber: Encode + Zero, + DomainHash: Clone + Encode + Default, +{ pub fn domain_id(&self) -> DomainId { match self { Self::InvalidStateTransition(proof) => proof.domain_id, @@ -289,8 +351,13 @@ impl FraudProof { bad_receipt_hash, .. } => *bad_receipt_hash, FraudProof::InvalidTotalRewards(proof) => proof.bad_receipt_hash(), - // TODO: Remove default value when invalid bundle proofs are fully expanded - FraudProof::InvalidBundles(_) => Default::default(), + FraudProof::InvalidBundles(proof) => match proof { + InvalidBundlesFraudProof::MissingInvalidBundleEntry(proof) => { + proof.bad_receipt_hash + } + // TODO: Return bad receipt hash + InvalidBundlesFraudProof::ValidAsInvalid(_) => Default::default(), + }, FraudProof::InvalidExtrinsicsRoot(proof) => proof.bad_receipt_hash, } } @@ -299,19 +366,13 @@ impl FraudProof { pub fn dummy_fraud_proof( domain_id: DomainId, bad_receipt_hash: ReceiptHash, - ) -> FraudProof { + ) -> FraudProof { FraudProof::Dummy { domain_id, bad_receipt_hash, } } -} -impl FraudProof -where - Number: Encode, - Hash: Encode, -{ pub fn hash(&self) -> H256 { ::hash(&self.encode()) } @@ -499,3 +560,12 @@ pub fn operator_block_rewards_final_key() -> Vec { frame_support::storage::storage_prefix("OperatorRewards".as_ref(), "BlockRewards".as_ref()) .to_vec() } + +type ExecutionReceiptInConsensusRuntime = + ExecutionReceipt, ::Hash, DomainNumber, DomainHash, Balance>; +sp_api::decl_runtime_apis! { + /// API related to execution receipts stored in runtime + pub trait ExecutionReceiptApi { + fn get_execution_receipt_by_hash(hash: ReceiptHash) -> Option>; + } +} diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index dd80021ce2..7b19af6e13 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -21,6 +21,7 @@ pub mod bundle_producer_election; pub mod fraud_proof; pub mod inherents; pub mod merkle_tree; +pub mod storage_proof; #[cfg(test)] mod tests; pub mod transaction; @@ -555,6 +556,18 @@ pub type EpochIndex = u32; /// Type representing operator ID pub type OperatorId = u64; +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] +pub struct RuntimeObject { + pub runtime_name: Vec, + pub runtime_type: RuntimeType, + pub runtime_upgrades: u32, + pub hash: Hash, + pub code: Vec, + pub version: RuntimeVersion, + pub created_at: Number, + pub updated_at: Number, +} + /// Staking specific hold identifier #[derive( PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, Ord, PartialOrd, Copy, Debug, diff --git a/crates/sp-domains/src/storage_proof.rs b/crates/sp-domains/src/storage_proof.rs new file mode 100644 index 0000000000..f604106910 --- /dev/null +++ b/crates/sp-domains/src/storage_proof.rs @@ -0,0 +1,294 @@ +use crate::{DomainId, OpaqueBundle, RuntimeId, RuntimeObject}; +use frame_support::storage::generator::StorageMap; +use frame_support::{Identity, PalletError}; +use hash_db::Hasher; +use parity_scale_codec::{Decode, Encode, FullCodec}; +use scale_info::TypeInfo; +use sp_core::storage::StorageKey; +use sp_core::H256; +use sp_runtime::traits::{Block as BlockT, HashingFor, NumberFor}; +use sp_std::marker::PhantomData; +use sp_std::vec::Vec; +use sp_trie::{read_trie_value, LayoutV1, StorageProof}; + +#[cfg(feature = "std")] +use sc_client_api::ProofProvider; + +/// Verification error. +#[derive(Debug, PartialEq, Eq, Encode, Decode, PalletError, TypeInfo)] +#[cfg_attr(feature = "thiserror", derive(thiserror::Error))] +pub enum VerificationError { + #[cfg_attr(feature = "thiserror", error("The expected state root doesn't exist"))] + InvalidStateRoot, + #[cfg_attr(feature = "thiserror", error("The given storage proof is invalid"))] + InvalidProof, + #[cfg_attr( + feature = "thiserror", + error("Value doesn't exist in the Db for the given key") + )] + MissingValue, + #[cfg_attr(feature = "thiserror", error("Failed to decode value."))] + FailedToDecode, + #[cfg_attr( + feature = "thiserror", + error("The given bundle storage proof is invalid") + )] + InvalidBundleStorageProof, + #[cfg_attr( + feature = "thiserror", + error("The given domain runtime code storage proof is invalid") + )] + InvalidDomainRuntimeCodeStorageProof, +} + +pub struct StorageProofVerifier(PhantomData); + +impl StorageProofVerifier { + pub fn verify_and_get_value( + state_root: &H::Out, + proof: StorageProof, + key: StorageKey, + ) -> Result { + let db = proof.into_memory_db::(); + let val = read_trie_value::, _>(&db, state_root, key.as_ref(), None, None) + .map_err(|_| VerificationError::InvalidProof)? + .ok_or(VerificationError::MissingValue)?; + + let decoded = V::decode(&mut &val[..]).map_err(|_| VerificationError::FailedToDecode)?; + + Ok(decoded) + } +} + +/// This is a representation of actual `SuccessfulBundle` storage in pallet-domains. +/// Any change in key or value there should be changed here accordingly. +pub struct SuccessfulBundlesStorage; +impl StorageMap> for SuccessfulBundlesStorage { + type Query = Vec; + type Hasher = Identity; + + fn module_prefix() -> &'static [u8] { + "Domains".as_ref() + } + + fn storage_prefix() -> &'static [u8] { + "SuccessfulBundles".as_ref() + } + + fn from_optional_value_to_query(v: Option>) -> Self::Query { + v.unwrap_or(Default::default()) + } + + fn from_query_to_optional_value(v: Self::Query) -> Option> { + Some(v) + } +} + +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct OpaqueBundleWithProof { + /// The targetted bundle + pub bundle: OpaqueBundle, + /// The index of the bundle + pub bundle_index: u32, + /// Storage witness of the targetted valid bundle + pub bundle_storage_proof: StorageProof, +} + +impl + OpaqueBundleWithProof +where + Number: Encode, + Hash: Encode, + DomainNumber: Encode, + DomainHash: Encode, + Balance: Encode, +{ + /// Generate `OpaqueBundleWithProof` + /// + /// - `block_hash`: the hash of the block that included the `bundle` in the `SuccessfulBundles` state + /// - `bundle`: the bundle included in `block_hash` + /// - `bundle_index`: the index of the `bundle` in the `SuccessfulBundles` state + #[cfg(feature = "std")] + #[allow(clippy::let_and_return)] + pub fn generate>( + proof_provider: &PP, + domain_id: DomainId, + block_hash: Block::Hash, + bundle: OpaqueBundle, + bundle_index: u32, + ) -> Result { + let bundle_storage_proof = { + let key = SuccessfulBundlesStorage::storage_map_final_key(domain_id); + let proof = proof_provider.read_proof(block_hash, &mut [key.as_slice()].into_iter())?; + proof + }; + Ok(OpaqueBundleWithProof { + bundle, + bundle_index, + bundle_storage_proof, + }) + } + + /// Verify if the `bundle` does commit to the given `state_root` + pub fn verify( + &self, + domain_id: DomainId, + state_root: &Block::Hash, + ) -> Result<(), VerificationError> { + let storage_key = SuccessfulBundlesStorage::storage_map_final_key(domain_id); + let successful_bundles_at: Vec = + StorageProofVerifier::>::verify_and_get_value( + state_root, + self.bundle_storage_proof.clone(), + StorageKey(storage_key), + )?; + + successful_bundles_at + .get(self.bundle_index as usize) + .filter(|b| **b == self.bundle.hash()) + .ok_or(VerificationError::InvalidBundleStorageProof)?; + + Ok(()) + } +} + +/// This is a representation of actual `DomainRuntimeMap` storage in pallet-domains. +/// Any change in key or value there should be changed here accordingly. +pub struct DomainRuntimeMapStorage; +impl StorageMap for DomainRuntimeMapStorage { + type Query = Option; + type Hasher = Identity; + + fn module_prefix() -> &'static [u8] { + "Domains".as_ref() + } + + fn storage_prefix() -> &'static [u8] { + "DomainRuntimeMap".as_ref() + } + + fn from_optional_value_to_query(v: Option) -> Self::Query { + v + } + + fn from_query_to_optional_value(v: Self::Query) -> Option { + v + } +} + +/// This is a representation of actual `RuntimeRegistry` storage in pallet-domains. +/// Any change in key or value there should be changed here accordingly. +pub struct RuntimeRegistryStorage(PhantomData<(Number, Hash)>); +impl StorageMap> + for RuntimeRegistryStorage +where + Number: FullCodec + TypeInfo + 'static, + Hash: FullCodec + TypeInfo + 'static, +{ + type Query = Option>; + type Hasher = Identity; + + fn module_prefix() -> &'static [u8] { + "Domains".as_ref() + } + + fn storage_prefix() -> &'static [u8] { + "RuntimeRegistry".as_ref() + } + + fn from_optional_value_to_query(v: Option>) -> Self::Query { + v + } + + fn from_query_to_optional_value(v: Self::Query) -> Option> { + v + } +} + +// The domain runtime code with storage proof +// +// NOTE: usually we should use the parent consensus block hash to `generate` or `verify` the +// domain runtime code because the domain's `set_code` extrinsic is always the last extrinsic +// to execute thus the domain runtime code will take effect in the next domain block, in other +// word the domain runtime code of the parent consensus block is the one used when construting +// the `ExecutionReceipt`. +#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] +pub struct DomainRuntimeCodeWithProof { + /// Storage witness of the `domain_id` -> `runtime_id` in the `DomainRuntimeMap` state + pub domain_runtime_storage_proof: StorageProof, + /// Storage witness of the `RuntimeObject` in the `RuntimeRegistry` state + pub runtime_registry_storage_proof: StorageProof, +} + +impl DomainRuntimeCodeWithProof { + /// Generate `DomainRuntimeCodeWithProof` + /// + /// - `runtime_id`: the `RuntimeId` of the domain runtime + /// - `block_hash`: the consensus block hash used to get domain runtime code + /// + /// NOTE: `block_hash` usually should be the parent hash of `ER::consensus_block_hash` + /// see the comment of `DomainRuntimeCodeWithProof` for more detail. + #[cfg(feature = "std")] + #[allow(clippy::let_and_return)] + pub fn generate>( + proof_provider: &PP, + domain_id: DomainId, + runtime_id: RuntimeId, + block_hash: Block::Hash, + ) -> Result { + let domain_runtime_storage_proof = { + let key = DomainRuntimeMapStorage::storage_map_final_key(domain_id); + let proof = proof_provider.read_proof(block_hash, &mut [key.as_slice()].into_iter())?; + proof + }; + let runtime_registry_storage_proof = { + let key = + RuntimeRegistryStorage::, Block::Hash>::storage_map_final_key( + runtime_id, + ); + let proof = proof_provider.read_proof(block_hash, &mut [key.as_slice()].into_iter())?; + proof + }; + Ok(DomainRuntimeCodeWithProof { + domain_runtime_storage_proof, + runtime_registry_storage_proof, + }) + } + + /// Verify if the domain runtime code does commit to the given `state_root` + /// + /// NOTE: `state_root` usually should be the state root of the parent block + /// of `ER::consensus_block_hash`, see the comment of `DomainRuntimeCodeWithProof` + /// for more detail. + pub fn verify( + &self, + domain_id: DomainId, + state_root: &Block::Hash, + ) -> Result, VerificationError> { + let runtime_id = { + let storage_key = DomainRuntimeMapStorage::storage_map_final_key(domain_id); + StorageProofVerifier::>::verify_and_get_value::( + state_root, + self.domain_runtime_storage_proof.clone(), + StorageKey(storage_key), + )? + }; + + let runtime_obj = { + let storage_key = + RuntimeRegistryStorage::, Block::Hash>::storage_map_final_key( + runtime_id, + ); + + StorageProofVerifier::>::verify_and_get_value::< + RuntimeObject, Block::Hash>, + >( + state_root, + self.runtime_registry_storage_proof.clone(), + StorageKey(storage_key), + )? + }; + + Ok(runtime_obj.code) + } +} diff --git a/crates/sp-domains/src/transaction.rs b/crates/sp-domains/src/transaction.rs index 434d2974fa..05e1bb3805 100644 --- a/crates/sp-domains/src/transaction.rs +++ b/crates/sp-domains/src/transaction.rs @@ -40,7 +40,7 @@ where Block: BlockT, { Null, - FraudProof(FraudProof, Block::Hash>), + FraudProof(FraudProof, Block::Hash, DomainNumber, DomainHash>), Bundle(OpaqueBundle, Block::Hash, DomainNumber, DomainHash, Balance>), } diff --git a/crates/subspace-fraud-proof/Cargo.toml b/crates/subspace-fraud-proof/Cargo.toml index f3961883fc..f8ced4bccc 100644 --- a/crates/subspace-fraud-proof/Cargo.toml +++ b/crates/subspace-fraud-proof/Cargo.toml @@ -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 = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } +sc-executor = { version = "0.10.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-api = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-blockchain = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-core = { version = "21.0.0", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } @@ -28,9 +29,11 @@ sp-messenger = { version = "0.1.0", path = "../../domains/primitives/messenger" sp-runtime = { version = "24.0.0", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-state-machine = { version = "0.28.0", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } sp-trie = { version = "22.0.0", git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } +subspace-core-primitives = { path = "../../crates/subspace-core-primitives" } 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 = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } diff --git a/crates/subspace-fraud-proof/src/invalid_bundles_fraud_proof.rs b/crates/subspace-fraud-proof/src/invalid_bundles_fraud_proof.rs new file mode 100644 index 0000000000..d1868f015a --- /dev/null +++ b/crates/subspace-fraud-proof/src/invalid_bundles_fraud_proof.rs @@ -0,0 +1,243 @@ +//! Invalid bundle proof for the `invalid_bundles` field of the execution receipt. + +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; +use sp_domains::fraud_proof::{ + ConsensusBlockDetails, ExecutionReceiptApi, InvalidBundlesFraudProof, + MissingBundleAdditionalData, MissingInvalidBundleEntryFraudProof, VerificationError, +}; +use sp_domains::storage_proof::OpaqueBundleWithProof; +use sp_domains::{DomainsApi, InvalidBundleType}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use std::marker::PhantomData; +use std::sync::Arc; +use subspace_core_primitives::U256; + +/// Valid bundle proof verifier. +pub struct InvalidBundleProofVerifier { + consensus_client: Arc, + executor: Arc, + _phantom: PhantomData<(CBlock, DomainBlock)>, +} + +impl Clone + for InvalidBundleProofVerifier +{ + fn clone(&self) -> Self { + Self { + consensus_client: self.consensus_client.clone(), + executor: self.executor.clone(), + _phantom: self._phantom, + } + } +} + +impl + InvalidBundleProofVerifier +where + CBlock: BlockT, + DomainBlock: BlockT, + CClient: HeaderBackend + ProvideRuntimeApi + Send + Sync, + CClient::Api: DomainsApi, ::Hash> + + ExecutionReceiptApi, ::Hash>, + Exec: CodeExecutor + 'static + RuntimeVersionOf, +{ + /// Constructs a new instance of [`InvalidBundleProofVerifier`]. + pub fn new(consensus_client: Arc, executor: Arc) -> Self { + Self { + consensus_client, + executor, + _phantom: Default::default(), + } + } + + /// Verify the `InvalidBundleProof` + pub fn verify( + &self, + invalid_bundle_proof: &InvalidBundlesFraudProof< + NumberFor, + CBlock::Hash, + NumberFor, + ::Hash, + >, + ) -> Result<(), VerificationError> { + match invalid_bundle_proof { + InvalidBundlesFraudProof::MissingInvalidBundleEntry(proof) => { + let MissingInvalidBundleEntryFraudProof { + domain_id, + consensus_block_details, + runtime_code_with_proof, + opaque_bundle_with_proof, + additional_data, + bad_receipt_hash, + bundle_index, + .. + } = proof; + + let ConsensusBlockDetails { + consensus_block_incl_bundle: consensus_block_hash_incl_bundle, + consensus_block_incl_er: consensus_block_hash_incl_er, + } = consensus_block_details; + + 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")) + })? + }; + + // Retrieve parent domain block hash. We are assuming here verification client has + // the bad receipt and its parent in state. + let bad_receipt = self + .consensus_client + .runtime_api() + .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_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::( + *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 + // or not part of the invalid bundles array. + let maybe_target_invalid_bundle = bad_receipt + .invalid_bundles + .iter() + .find(|b| b.bundle_index == *bundle_index); + // If there is a target invalid bundle means execution receipt contains a invalid bundle + // with wrong bundle type. Otherwise a invalid bundle with out of range tx is considered + // as valid. In both cases, proof would be the same. + if let Some(target_invalid_bundle) = maybe_target_invalid_bundle { + if target_invalid_bundle.invalid_bundle_type == InvalidBundleType::OutOfRangeTx + { + return Err(VerificationError::IncorrectBundleInFraudProof { + bundle_index: *bundle_index, + }); + } + } + + // 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 = runtime_code_with_proof.verify::( + *domain_id, + parent_consensus_block_header_incl_bundle.state_root(), + )?; + + let runtime_api_light = + RuntimeApiLight::new(self.executor.clone(), domain_runtime_code.into()); + + match additional_data { + MissingBundleAdditionalData::OutOfRangeTx { extrinsic_index } => { + let opaque_extrinsic = bundle + .extrinsics + .get(*extrinsic_index as usize) + .ok_or(VerificationError::DomainExtrinsicNotFound(*extrinsic_index))?; + + let extrinsic = <::Extrinsic>::decode( + &mut opaque_extrinsic.encode().as_slice(), + )?; + + let tx_range = self + .consensus_client + .runtime_api() + .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(), + ); + + let is_within_tx_range = + as domain_runtime_primitives::DomainCoreApi< + DomainBlock, + >>::is_within_tx_range( + &runtime_api_light, + parent_domain_block_hash, + &extrinsic, + &bundle_vrf_hash, + &tx_range, + )?; + + if is_within_tx_range { + return Err(VerificationError::TxIsInRange { + extrinsic_index: *extrinsic_index, + }); + } + } + } + } + // TODO: Add verification for valid as invalid fraud proof here + InvalidBundlesFraudProof::ValidAsInvalid(_) => {} + } + + Ok(()) + } +} + +/// Verifies valid bundle proof. +pub trait VerifyInvalidBundleProof { + /// Returns `Ok(())` if given `valid_bundle_proof` is legitimate. + fn verify_invalid_bundle_proof( + &self, + invalid_bundle_proof: &InvalidBundlesFraudProof< + NumberFor, + CBlock::Hash, + NumberFor, + ::Hash, + >, + ) -> Result<(), VerificationError>; +} + +impl VerifyInvalidBundleProof + for InvalidBundleProofVerifier +where + CBlock: BlockT, + DomainBlock: BlockT, + Client: HeaderBackend + ProvideRuntimeApi + Send + Sync, + Client::Api: DomainsApi, ::Hash> + + ExecutionReceiptApi, ::Hash>, + Exec: CodeExecutor + 'static + RuntimeVersionOf, +{ + fn verify_invalid_bundle_proof( + &self, + invalid_bundle_proof: &InvalidBundlesFraudProof< + NumberFor, + CBlock::Hash, + NumberFor, + ::Hash, + >, + ) -> Result<(), VerificationError> { + self.verify(invalid_bundle_proof) + } +} diff --git a/crates/subspace-fraud-proof/src/invalid_transaction_proof.rs b/crates/subspace-fraud-proof/src/invalid_transaction_proof.rs index 25ce059403..be2c41ed27 100644 --- a/crates/subspace-fraud-proof/src/invalid_transaction_proof.rs +++ b/crates/subspace-fraud-proof/src/invalid_transaction_proof.rs @@ -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; @@ -52,7 +53,7 @@ fn create_runtime_api_light( extrinsic: OpaqueExtrinsic, ) -> Result, VerificationError> where - Exec: CodeExecutor, + Exec: CodeExecutor + RuntimeVersionOf, { let mut runtime_api_light = RuntimeApiLight::new(executor, wasm_bundle); @@ -83,7 +84,7 @@ where for storage_key in storage_keys.into_iter() { let storage_value = read_trie_value::, _>(&db, state_root, &storage_key, None, None) - .map_err(|_| VerificationError::InvalidStorageProof)? + .map_err(|_| sp_domains::storage_proof::VerificationError::InvalidProof)? .ok_or_else(|| VerificationError::StateNotFound(storage_key.clone()))?; top_storage_map.insert(storage_key, storage_value); } @@ -107,7 +108,7 @@ where CClient: HeaderBackend + ProvideRuntimeApi + Send + Sync, CClient::Api: DomainsApi, VerifierClient: VerifierApi, - Exec: CodeExecutor + 'static, + Exec: CodeExecutor + 'static + RuntimeVersionOf, { /// Constructs a new instance of [`InvalidTransactionProofVerifier`]. pub fn new( @@ -224,7 +225,7 @@ where Client: HeaderBackend + ProvideRuntimeApi + Send + Sync, Client::Api: DomainsApi, VerifierClient: VerifierApi, - Exec: CodeExecutor + 'static, + Exec: CodeExecutor + 'static + RuntimeVersionOf, { fn verify_invalid_transaction_proof( &self, diff --git a/crates/subspace-fraud-proof/src/lib.rs b/crates/subspace-fraud-proof/src/lib.rs index 41245aea53..5cad040cd6 100644 --- a/crates/subspace-fraud-proof/src/lib.rs +++ b/crates/subspace-fraud-proof/src/lib.rs @@ -3,12 +3,14 @@ #![warn(missing_docs)] mod domain_runtime_code; +pub mod invalid_bundles_fraud_proof; pub mod invalid_state_transition_proof; pub mod invalid_transaction_proof; #[cfg(test)] mod tests; pub mod verifier_api; +use crate::invalid_bundles_fraud_proof::VerifyInvalidBundleProof; use futures::channel::oneshot; use futures::FutureExt; use invalid_state_transition_proof::VerifyInvalidStateTransitionProof; @@ -22,23 +24,29 @@ use std::sync::Arc; /// Verify fraud proof. /// /// Verifier is either the primary chain client or the system domain client. -pub trait VerifyFraudProof { +pub trait VerifyFraudProof { /// Verifies fraud proof. fn verify_fraud_proof( &self, - proof: &FraudProof, VerifierBlock::Hash>, + proof: &FraudProof< + NumberFor, + VerifierBlock::Hash, + NumberFor, + DomainBlock::Hash, + >, ) -> Result<(), VerificationError>; } /// Fraud proof verifier. -pub struct ProofVerifier { +pub struct ProofVerifier { invalid_transaction_proof_verifier: Arc, invalid_state_transition_proof_verifier: Arc, - _phantom: PhantomData, + invalid_bundle_proof_verifier: Arc, + _phantom: PhantomData<(VerifierBlock, DomainBlock)>, } -impl Clone - for ProofVerifier +impl Clone + for ProofVerifier { fn clone(&self) -> Self { Self { @@ -46,26 +54,31 @@ impl Clone invalid_state_transition_proof_verifier: self .invalid_state_transition_proof_verifier .clone(), + invalid_bundle_proof_verifier: self.invalid_bundle_proof_verifier.clone(), _phantom: self._phantom, } } } -impl - ProofVerifier +impl + ProofVerifier where VerifierBlock: BlockT, + DomainBlock: BlockT, ITPVerifier: VerifyInvalidTransactionProof, ISTPVerifier: VerifyInvalidStateTransitionProof, + IBPVerifier: VerifyInvalidBundleProof, { /// Constructs a new instance of [`ProofVerifier`]. pub fn new( invalid_transaction_proof_verifier: Arc, invalid_state_transition_proof_verifier: Arc, + invalid_bundle_proof_verifier: Arc, ) -> Self { Self { invalid_transaction_proof_verifier, invalid_state_transition_proof_verifier, + invalid_bundle_proof_verifier, _phantom: Default::default(), } } @@ -73,7 +86,12 @@ where /// Verifies the fraud proof. pub fn verify( &self, - fraud_proof: &FraudProof, VerifierBlock::Hash>, + fraud_proof: &FraudProof< + NumberFor, + VerifierBlock::Hash, + NumberFor, + DomainBlock::Hash, + >, ) -> Result<(), VerificationError> { match fraud_proof { FraudProof::InvalidStateTransition(proof) => self @@ -82,35 +100,52 @@ where FraudProof::InvalidTransaction(proof) => self .invalid_transaction_proof_verifier .verify_invalid_transaction_proof(proof), + FraudProof::InvalidBundles(proof) => self + .invalid_bundle_proof_verifier + .verify_invalid_bundle_proof(proof), proof => unimplemented!("Can not verify {proof:?}"), } } } -impl VerifyFraudProof - for ProofVerifier +impl + VerifyFraudProof + for ProofVerifier where VerifierBlock: BlockT, + DomainBlock: BlockT, ITPVerifier: VerifyInvalidTransactionProof, ISTPVerifier: VerifyInvalidStateTransitionProof, + IBPVerifier: VerifyInvalidBundleProof, { fn verify_fraud_proof( &self, - proof: &FraudProof, VerifierBlock::Hash>, + proof: &FraudProof< + NumberFor, + VerifierBlock::Hash, + NumberFor, + DomainBlock::Hash, + >, ) -> Result<(), VerificationError> { self.verify(proof) } } /// Verifies the fraud proof extracted from extrinsic in the transaction pool. -pub async fn validate_fraud_proof_in_tx_pool( +pub async fn validate_fraud_proof_in_tx_pool( spawner: &dyn SpawnNamed, fraud_proof_verifier: Verifier, - fraud_proof: FraudProof, Block::Hash>, + fraud_proof: FraudProof< + NumberFor, + Block::Hash, + NumberFor, + DomainBlock::Hash, + >, ) -> Result<(), VerificationError> where Block: BlockT, - Verifier: VerifyFraudProof + Send + 'static, + DomainBlock: BlockT, + Verifier: VerifyFraudProof + Send + 'static, { let (verified_result_sender, verified_result_receiver) = oneshot::channel(); diff --git a/crates/subspace-fraud-proof/src/tests.rs b/crates/subspace-fraud-proof/src/tests.rs index 3359f716d4..a33789efe3 100644 --- a/crates/subspace-fraud-proof/src/tests.rs +++ b/crates/subspace-fraud-proof/src/tests.rs @@ -1,29 +1,37 @@ +use crate::invalid_bundles_fraud_proof::InvalidBundleProofVerifier; use crate::invalid_state_transition_proof::{ExecutionProver, InvalidStateTransitionProofVerifier}; use crate::invalid_transaction_proof::InvalidTransactionProofVerifier; use crate::verifier_api::VerifierApi; use crate::ProofVerifier; use codec::Encode; use domain_block_builder::{BlockBuilder, RecordProof}; +use domain_client_operator::aux_schema::InvalidBundlesMismatchType; +use domain_client_operator::fraud_proof::FraudProofGenerator; +use domain_runtime_primitives::opaque::Block as DomainBlock; use domain_runtime_primitives::{DomainCoreApi, Hash}; use domain_test_service::domain::EvmDomainClient as DomainClient; use domain_test_service::evm_domain_test_runtime::Header; use domain_test_service::EcdsaKeyring::{Alice, Bob, Charlie, Dave}; -use domain_test_service::Sr25519Keyring::Ferdie; +use domain_test_service::Sr25519Keyring::{self, Ferdie}; use domain_test_service::GENESIS_DOMAIN_ID; +use frame_support::assert_ok; use sc_client_api::{HeaderBackend, StorageProof}; use sc_service::{BasePath, Role}; use sp_api::ProvideRuntimeApi; -use sp_core::H256; +use sp_core::{Pair, H256}; use sp_domain_digests::AsPredigest; use sp_domains::fraud_proof::{ - ExecutionPhase, FraudProof, InvalidStateTransitionProof, VerificationError, + ExecutionPhase, FraudProof, InvalidBundlesFraudProof, InvalidStateTransitionProof, + VerificationError, }; -use sp_domains::DomainId; +use sp_domains::{DomainId, InvalidBundle, InvalidBundleType}; use sp_runtime::generic::{Digest, DigestItem}; use sp_runtime::traits::{BlakeTwo256, Header as HeaderT}; use std::sync::Arc; +use subspace_core_primitives::U256; use subspace_runtime_primitives::opaque::Block; use subspace_test_client::Client; +use subspace_test_runtime::test_runtime_extension::pallet_test_override; use subspace_test_service::{produce_block_with, produce_blocks, MockConsensusNode}; use tempfile::TempDir; @@ -91,6 +99,182 @@ impl VerifierApi for TestVerifierClient { // Use the system domain id for testing const TEST_DOMAIN_ID: DomainId = DomainId::new(3u32); +#[tokio::test(flavor = "multi_thread")] +async fn test_valid_bundle_proof_generation_and_verification() { + let directory = TempDir::new().expect("Must be able to create temporary directory"); + + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let tokio_handle = tokio::runtime::Handle::current(); + + let change_tx_range = |new_range| { + subspace_test_runtime::UncheckedExtrinsic::new_unsigned( + pallet_test_override::Call::override_tx_range { new_range }.into(), + ) + .into() + }; + + // Start Ferdie + let mut ferdie = MockConsensusNode::run( + tokio_handle.clone(), + Ferdie, + BasePath::new(directory.path().join("ferdie")), + ); + + // Produce 1 consensus block to initialize genesis domain + ferdie.produce_block_with_slot(1.into()).await.unwrap(); + + // Run Alice (a evm domain authority node) + let mut alice = domain_test_service::DomainNodeBuilder::new( + tokio_handle.clone(), + Alice, + BasePath::new(directory.path().join("alice")), + ) + .build_evm_node(Role::Authority, GENESIS_DOMAIN_ID, &mut ferdie) + .await; + + let fraud_proof_generator = FraudProofGenerator::new( + alice.client.clone(), + ferdie.client.clone(), + alice.backend.clone(), + alice.code_executor.clone(), + ); + let invalid_bundle_proof_verifier = InvalidBundleProofVerifier::<_, DomainBlock, _, _>::new( + ferdie.client.clone(), + alice.code_executor.clone(), + ); + + for i in 0..3 { + let tx = alice.construct_extrinsic( + alice.account_nonce() + i, + pallet_balances::Call::transfer_allow_death { + dest: Bob.to_account_id(), + value: 1, + }, + ); + alice + .send_extrinsic(tx) + .await + .expect("Failed to send extrinsic"); + + // Produce a bundle and submit to the tx pool of the consensus node + let (slot, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + assert!(bundle.is_some()); + + // In the last iteration, produce a consensus block which will included all the previous bundles + if i == 2 { + ferdie + .submit_transaction(change_tx_range(U256::one())) + .await + .unwrap(); + + produce_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + } + } + + let bundle_to_tx = |opaque_bundle| { + subspace_test_runtime::UncheckedExtrinsic::new_unsigned( + pallet_domains::Call::submit_bundle { opaque_bundle }.into(), + ) + .into() + }; + + // Produce a bundle that will include the reciept of the last 3 bundles and modified the receipt's + // `valid_bundles` field to make it invalid + let (slot, bundle) = ferdie.produce_slot_and_wait_for_bundle_submission().await; + let original_submit_bundle_tx = bundle_to_tx(bundle.clone().unwrap()); + let bundle_index = 0; + let (bad_receipt, valid_receipt, submit_bundle_tx_with_bad_receipt) = { + let mut bundle = bundle.clone().unwrap(); + let valid_receipt = bundle.receipt().clone(); + assert_eq!(valid_receipt.valid_bundles.len(), 0); + assert_eq!(valid_receipt.invalid_bundles.len(), 3); + for invalid_bundle in valid_receipt.invalid_bundles.iter() { + assert_eq!( + invalid_bundle.invalid_bundle_type, + InvalidBundleType::OutOfRangeTx + ); + } + + bundle.sealed_header.header.receipt.invalid_bundles[0] = InvalidBundle { + bundle_index: bundle.sealed_header.header.receipt.invalid_bundles[0].bundle_index, + invalid_bundle_type: InvalidBundleType::IllegalTx, + }; + + bundle.sealed_header.signature = Sr25519Keyring::Alice + .pair() + .sign(bundle.sealed_header.pre_hash().as_ref()) + .into(); + + ( + bundle.receipt().clone(), + valid_receipt, + bundle_to_tx(bundle), + ) + }; + // Replace `original_submit_bundle_tx` with `submit_bundle_tx_with_bad_receipt` in the tx pool + ferdie + .prune_tx_from_pool(&original_submit_bundle_tx) + .await + .unwrap(); + assert!(ferdie.get_bundle_from_tx_pool(slot.into()).is_none()); + ferdie + .submit_transaction(submit_bundle_tx_with_bad_receipt) + .await + .unwrap(); + + // Produce one more block to inlcude the bad receipt in the consensus chain + produce_block_with!(ferdie.produce_block_with_slot(slot), alice) + .await + .unwrap(); + + // TODO: let the operator construct the valid bundle fraud proof by it own once the `valid_bundles` + // fraud detection is implemented + let invalid_bundle_proof = fraud_proof_generator + .generate_invalid_bundle_field_proof( + GENESIS_DOMAIN_ID, + &valid_receipt, + InvalidBundlesMismatchType::InvalidAsValid, + bundle_index as u32, + bad_receipt.hash(), + ) + .unwrap(); + + match invalid_bundle_proof { + FraudProof::InvalidBundles(InvalidBundlesFraudProof::MissingInvalidBundleEntry( + inner_proof, + )) => { + // If the fraud proof target a valid bundle it is considered invalid + let mut bad_proof = inner_proof.clone(); + bad_proof.bundle_index = 1; + assert!(invalid_bundle_proof_verifier + .verify(&InvalidBundlesFraudProof::MissingInvalidBundleEntry( + bad_proof, + )) + .is_err()); + + // If the fraud proof point to non-exist bundle then it is invalid + let mut bad_proof = inner_proof.clone(); + bad_proof.opaque_bundle_with_proof.bundle.extrinsics = Default::default(); + assert!(invalid_bundle_proof_verifier + .verify(&InvalidBundlesFraudProof::MissingInvalidBundleEntry( + bad_proof, + )) + .is_err()); + + // The original fraud proof is valid + assert_ok!(invalid_bundle_proof_verifier.verify( + &InvalidBundlesFraudProof::MissingInvalidBundleEntry(inner_proof,) + )); + } + _ => unreachable!("Unexpected fraud proof"), + } +} + #[tokio::test(flavor = "multi_thread")] #[ignore] async fn execution_proof_creation_and_verification_should_work() { @@ -268,9 +452,13 @@ async fn execution_proof_creation_and_verification_should_work() { TestVerifierClient::new(ferdie.client.clone(), alice.client.clone()), ); - let proof_verifier = ProofVerifier::::new( + let invalid_bundle_proof_verifier = + InvalidBundleProofVerifier::new(ferdie.client.clone(), Arc::new(ferdie.executor.clone())); + + let proof_verifier = ProofVerifier::::new( Arc::new(invalid_transaction_proof_verifier), Arc::new(invalid_state_transition_proof_verifier), + Arc::new(invalid_bundle_proof_verifier), ); let parent_number_alice = *parent_header.number(); @@ -555,9 +743,13 @@ async fn invalid_execution_proof_should_not_work() { TestVerifierClient::new(ferdie.client.clone(), alice.client.clone()), ); - let proof_verifier = ProofVerifier::::new( + let invalid_bundle_proof_verifier = + InvalidBundleProofVerifier::new(ferdie.client.clone(), Arc::new(ferdie.executor.clone())); + + let proof_verifier = ProofVerifier::::new( Arc::new(invalid_transaction_proof_verifier), Arc::new(invalid_state_transition_proof_verifier), + Arc::new(invalid_bundle_proof_verifier), ); let parent_number_alice = *parent_header.number(); diff --git a/crates/subspace-runtime/src/domains.rs b/crates/subspace-runtime/src/domains.rs index 7959eb849c..ec74c28e32 100644 --- a/crates/subspace-runtime/src/domains.rs +++ b/crates/subspace-runtime/src/domains.rs @@ -50,7 +50,7 @@ pub(crate) fn extract_receipts( pub(crate) fn extract_fraud_proofs( extrinsics: Vec, domain_id: DomainId, -) -> Vec> { +) -> Vec> { // TODO: Ensure fraud proof extrinsic is infallible. extrinsics .into_iter() diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 639507c6d6..655f108bca 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -63,8 +63,8 @@ use sp_core::storage::{StateVersion, StorageKey}; use sp_core::{OpaqueMetadata, H256}; use sp_domains::bundle_producer_election::BundleProducerElectionParams; use sp_domains::{ - DomainId, DomainInstanceData, DomainsHoldIdentifier, OperatorId, OperatorPublicKey, - StakingHoldIdentifier, + DomainId, DomainInstanceData, DomainsHoldIdentifier, ExecutionReceipt, OperatorId, + OperatorPublicKey, ReceiptHash, StakingHoldIdentifier, }; use sp_messenger::endpoint::{Endpoint, EndpointHandler as EndpointHandlerT, EndpointId}; use sp_messenger::messages::{ @@ -1133,6 +1133,12 @@ impl_runtime_apis! { } } + impl sp_domains::fraud_proof::ExecutionReceiptApi for Runtime { + fn get_execution_receipt_by_hash(hash: ReceiptHash) -> Option, ::Hash, DomainNumber, DomainHash, Balance>> { + Domains::execution_receipt_by_hash(hash) + } + } + impl sp_session::SessionKeys for Runtime { fn generate_session_keys(seed: Option>) -> Vec { SessionKeys::generate(seed) diff --git a/crates/subspace-service/src/lib.rs b/crates/subspace-service/src/lib.rs index a2f434fe7d..b72a7d13f4 100644 --- a/crates/subspace-service/src/lib.rs +++ b/crates/subspace-service/src/lib.rs @@ -35,6 +35,7 @@ use crate::metrics::NodeMetrics; use crate::tx_pre_validator::ConsensusChainTxPreValidator; use core::sync::atomic::{AtomicU32, Ordering}; use cross_domain_message_gossip::cdm_gossip_peers_set_config; +use domain_runtime_primitives::opaque::Block as DomainBlock; use domain_runtime_primitives::{BlockNumber as DomainNumber, Hash as DomainHash}; pub use dsn::DsnConfig; use frame_system_rpc_runtime_api::AccountNonceApi; @@ -78,6 +79,7 @@ use sp_consensus_subspace::{ FarmerPublicKey, KzgExtension, PosExtension, PotExtension, PotNextSlotInput, SubspaceApi, }; use sp_core::traits::SpawnEssentialNamed; +use sp_domains::fraud_proof::ExecutionReceiptApi; use sp_domains::transaction::PreValidationObjectApi; use sp_domains::{DomainsApi, GenerateGenesisStateRoot, GenesisReceiptExtension}; use sp_externalities::Extensions; @@ -94,6 +96,7 @@ use std::sync::Arc; use std::time::Duration; use subspace_core_primitives::crypto::kzg::{embedded_kzg_settings, Kzg}; use subspace_core_primitives::{PotSeed, REWARD_SIGNING_CONTEXT}; +use subspace_fraud_proof::invalid_bundles_fraud_proof::InvalidBundleProofVerifier; use subspace_fraud_proof::verifier_api::VerifierClient; use subspace_metrics::{start_prometheus_metrics_server, RegistryAdapter}; use subspace_networking::libp2p::multiaddr::Protocol; @@ -181,8 +184,15 @@ pub type InvalidStateTransitionProofVerifier = pub type FraudProofVerifier = subspace_fraud_proof::ProofVerifier< Block, + DomainBlock, InvalidTransactionProofVerifier, InvalidStateTransitionProofVerifier, + InvalidBundleProofVerifier< + Block, + DomainBlock, + FullClient, + NativeElseWasmExecutor, + >, >; /// Subspace networking instantiation variant @@ -393,6 +403,7 @@ type PartialComponents = sc_service::PartialCompon FullClient, ConsensusChainTxPreValidator< Block, + DomainBlock, FullClient, FraudProofVerifier, >, @@ -427,7 +438,8 @@ where + SubspaceApi + DomainsApi + ObjectsApi - + PreValidationObjectApi, + + PreValidationObjectApi + + ExecutionReceiptApi, ExecutorDispatch: NativeExecutionDispatch + 'static, { let telemetry = config @@ -486,6 +498,9 @@ where VerifierClient::new(client.clone()), ); + let invalid_bundle_proof_verifier = + InvalidBundleProofVerifier::new(client.clone(), Arc::new(executor.clone())); + let invalid_state_transition_proof_verifier = InvalidStateTransitionProofVerifier::new( client.clone(), executor, @@ -495,6 +510,7 @@ where let proof_verifier = subspace_fraud_proof::ProofVerifier::new( Arc::new(invalid_transaction_proof_verifier), Arc::new(invalid_state_transition_proof_verifier), + Arc::new(invalid_bundle_proof_verifier), ); let tx_pre_validator = ConsensusChainTxPreValidator::new( @@ -659,6 +675,7 @@ type FullNode = NewFull< FullClient, ConsensusChainTxPreValidator< Block, + DomainBlock, FullClient, FraudProofVerifier, >, @@ -688,7 +705,8 @@ where + SubspaceApi + DomainsApi + ObjectsApi - + PreValidationObjectApi, + + PreValidationObjectApi + + ExecutionReceiptApi, ExecutorDispatch: NativeExecutionDispatch + 'static, { let PartialComponents { diff --git a/crates/subspace-service/src/tx_pre_validator.rs b/crates/subspace-service/src/tx_pre_validator.rs index 4b1077aea0..c19ed50434 100644 --- a/crates/subspace-service/src/tx_pre_validator.rs +++ b/crates/subspace-service/src/tx_pre_validator.rs @@ -1,26 +1,27 @@ -use domain_runtime_primitives::{BlockNumber as DomainNumber, Hash as DomainHash}; use sc_transaction_pool::error::Result as TxPoolResult; use sc_transaction_pool_api::error::Error as TxPoolError; use sc_transaction_pool_api::TransactionSource; use sp_api::ProvideRuntimeApi; use sp_core::traits::SpawnNamed; +use sp_domains::fraud_proof::ExecutionReceiptApi; use sp_domains::transaction::{ InvalidTransactionCode, PreValidationObject, PreValidationObjectApi, }; -use sp_runtime::traits::Block as BlockT; +use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::marker::PhantomData; use std::sync::Arc; use subspace_fraud_proof::VerifyFraudProof; use subspace_transaction_pool::PreValidateTransaction; -pub struct ConsensusChainTxPreValidator { +pub struct ConsensusChainTxPreValidator { client: Arc, spawner: Box, fraud_proof_verifier: Verifier, - _phantom_data: PhantomData, + _phantom_data: PhantomData<(Block, DomainBlock)>, } -impl Clone for ConsensusChainTxPreValidator +impl Clone + for ConsensusChainTxPreValidator where Verifier: Clone, { @@ -34,7 +35,9 @@ where } } -impl ConsensusChainTxPreValidator { +impl + ConsensusChainTxPreValidator +{ pub fn new( client: Arc, spawner: Box, @@ -50,13 +53,15 @@ impl ConsensusChainTxPreValidator PreValidateTransaction - for ConsensusChainTxPreValidator +impl PreValidateTransaction + for ConsensusChainTxPreValidator where Block: BlockT, + DomainBlock: BlockT, Client: ProvideRuntimeApi + Send + Sync, - Client::Api: PreValidationObjectApi, - Verifier: VerifyFraudProof + Clone + Send + Sync + 'static, + Client::Api: PreValidationObjectApi, DomainBlock::Hash> + + ExecutionReceiptApi, DomainBlock::Hash>, + Verifier: VerifyFraudProof + Clone + Send + Sync + 'static, { type Block = Block; async fn pre_validate_transaction( @@ -79,7 +84,7 @@ where // TODO: perhaps move the bundle format check here } PreValidationObject::FraudProof(fraud_proof) => { - subspace_fraud_proof::validate_fraud_proof_in_tx_pool( + subspace_fraud_proof::validate_fraud_proof_in_tx_pool::( &self.spawner, self.fraud_proof_verifier.clone(), fraud_proof, diff --git a/domains/client/block-preprocessor/src/runtime_api_light.rs b/domains/client/block-preprocessor/src/runtime_api_light.rs index 69ba683668..6ea4cb887d 100644 --- a/domains/client/block-preprocessor/src/runtime_api_light.rs +++ b/domains/client/block-preprocessor/src/runtime_api_light.rs @@ -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; @@ -32,7 +32,7 @@ pub struct RuntimeApiLight { impl Core for RuntimeApiLight where Block: BlockT, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn __runtime_api_internal_call_api_at( &self, @@ -47,7 +47,7 @@ where impl DomainCoreApi for RuntimeApiLight where Block: BlockT, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn __runtime_api_internal_call_api_at( &self, @@ -63,7 +63,7 @@ impl MessengerApi> for RuntimeApiLight< where Block: BlockT, NumberFor: Codec, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn __runtime_api_internal_call_api_at( &self, @@ -83,7 +83,7 @@ impl FetchRuntimeCode for RuntimeApiLight { impl RuntimeApiLight where - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { /// Create a new instance of [`RuntimeApiLight`] with empty storage. pub fn new(executor: Arc, runtime_code: Cow<'static, [u8]>) -> Self { @@ -110,29 +110,26 @@ where } } - fn runtime_version(&self) -> Result { - 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, ) -> Result, 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, @@ -148,7 +145,7 @@ where impl StateRootExtractor for RuntimeApiLight where Block: BlockT, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn extract_state_roots( &self, @@ -167,7 +164,7 @@ where impl InherentExtrinsicApi for RuntimeApiLight where Block: BlockT, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn __runtime_api_internal_call_api_at( &self, @@ -182,7 +179,7 @@ where impl SignerExtractor for RuntimeApiLight where Block: BlockT, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn extract_signer( &self, @@ -196,7 +193,7 @@ where impl SetCodeConstructor for RuntimeApiLight where Block: BlockT, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn construct_set_code_extrinsic( &self, @@ -210,7 +207,7 @@ where impl InherentExtrinsicConstructor for RuntimeApiLight where Block: BlockT, - Executor: CodeExecutor, + Executor: CodeExecutor + RuntimeVersionOf, { fn construct_timestamp_inherent_extrinsic( &self, diff --git a/domains/client/domain-operator/src/aux_schema.rs b/domains/client/domain-operator/src/aux_schema.rs index ca143f78bc..3d0dccaf8e 100644 --- a/domains/client/domain-operator/src/aux_schema.rs +++ b/domains/client/domain-operator/src/aux_schema.rs @@ -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, } diff --git a/domains/client/domain-operator/src/bundle_processor.rs b/domains/client/domain-operator/src/bundle_processor.rs index 5fd67adaa0..ab66c22529 100644 --- a/domains/client/domain-operator/src/bundle_processor.rs +++ b/domains/client/domain-operator/src/bundle_processor.rs @@ -152,6 +152,7 @@ where + BlockBackend + ProofProvider + ProvideRuntimeApi + + ProofProvider + 'static, CClient::Api: DomainsApi, Block::Hash> + MessengerApi> diff --git a/domains/client/domain-operator/src/domain_block_processor.rs b/domains/client/domain-operator/src/domain_block_processor.rs index 1ea2d1c44a..91a0222c73 100644 --- a/domains/client/domain-operator/src/domain_block_processor.rs +++ b/domains/client/domain-operator/src/domain_block_processor.rs @@ -1,7 +1,7 @@ use crate::aux_schema::{InvalidBundlesMismatchType, ReceiptMismatchInfo}; use crate::fraud_proof::{find_trace_mismatch, FraudProofGenerator}; use crate::parent_chain::ParentChainInterface; -use crate::utils::{DomainBlockImportNotification, DomainImportNotificationSinks}; +use crate::utils::{DomainBlockImportNotification, DomainImportNotificationSinks, FraudProofOf}; use crate::ExecutionReceiptFor; use codec::{Decode, Encode}; use domain_block_builder::{BlockBuilder, BuiltBlock, RecordProof}; @@ -678,7 +678,6 @@ impl where Block: BlockT, CBlock: BlockT, - ParentChainBlock: BlockT, NumberFor: Into>, Client: HeaderBackend + BlockBackend @@ -696,11 +695,12 @@ where CClient::Api: DomainsApi, Block::Hash>, Backend: sc_client_api::Backend + 'static, E: CodeExecutor, - ParentChain: ParentChainInterface, + ParentChain: ParentChainInterface, + ParentChainBlock: BlockT, { pub(crate) fn check_state_transition( &self, - parent_chain_block_hash: ParentChainBlock::Hash, + parent_chain_block_hash: CBlock::Hash, ) -> sp_blockchain::Result<()> { let extrinsics = self.parent_chain.block_body(parent_chain_block_hash)?; @@ -719,7 +719,7 @@ where pub(crate) fn submit_fraud_proof( &self, - parent_chain_block_hash: ParentChainBlock::Hash, + parent_chain_block_hash: CBlock::Hash, ) -> sp_blockchain::Result<()> { if self.consensus_network_sync_oracle.is_major_syncing() { tracing::debug!( @@ -743,8 +743,8 @@ where fn check_receipts( &self, - receipts: Vec>, - fraud_proofs: Vec, ParentChainBlock::Hash>>, + receipts: Vec>, + fraud_proofs: Vec>, ) -> Result<(), sp_blockchain::Error> { let mut bad_receipts_to_write = vec![]; @@ -757,20 +757,17 @@ where let consensus_block_hash = execution_receipt.consensus_block_hash; - let local_receipt = crate::aux_schema::load_execution_receipt::< - _, - Block, - ParentChainBlock, - >(&*self.client, consensus_block_hash)? + let local_receipt = crate::aux_schema::load_execution_receipt::<_, Block, CBlock>( + &*self.client, + consensus_block_hash, + )? .ok_or(sp_blockchain::Error::Backend(format!( "Receipt for consensus block #{},{consensus_block_hash} not found", execution_receipt.consensus_block_number )))?; - if let Some(receipt_mismatch_info) = verify_invalid_bundles_field::< - Block, - ParentChainBlock, - >(&local_receipt, execution_receipt) + if let Some(receipt_mismatch_info) = + verify_invalid_bundles_field::(&local_receipt, execution_receipt) { bad_receipts_to_write.push(( execution_receipt.consensus_block_number, @@ -844,7 +841,7 @@ where .collect::>(); for (bad_receipt_number, bad_receipt_hash, mismatch_info) in bad_receipts_to_write { - crate::aux_schema::write_bad_receipt::<_, ParentChainBlock>( + crate::aux_schema::write_bad_receipt::<_, CBlock>( &*self.client, bad_receipt_number, bad_receipt_hash, @@ -872,9 +869,7 @@ where fn create_fraud_proof_for_first_unconfirmed_bad_receipt( &self, - ) -> sp_blockchain::Result< - Option, ParentChainBlock::Hash>>, - > { + ) -> sp_blockchain::Result>> { if let Some((bad_receipt_hash, mismatch_info)) = crate::aux_schema::find_first_unconfirmed_bad_receipt_info::<_, Block, CBlock, _>( &*self.client, @@ -901,7 +896,7 @@ where let fraud_proof = match mismatch_info { ReceiptMismatchInfo::Trace { trace_index, .. } => self .fraud_proof_generator - .generate_invalid_state_transition_proof::( + .generate_invalid_state_transition_proof::( self.domain_id, trace_index, &local_receipt, @@ -914,7 +909,7 @@ where })?, ReceiptMismatchInfo::TotalRewards { .. } => self .fraud_proof_generator - .generate_invalid_total_rewards_proof::( + .generate_invalid_total_rewards_proof::( self.domain_id, &local_receipt, bad_receipt_hash, @@ -930,7 +925,7 @@ where .. } => self .fraud_proof_generator - .generate_invalid_bundle_field_proof::( + .generate_invalid_bundle_field_proof( self.domain_id, &local_receipt, mismatch_type, @@ -944,7 +939,7 @@ where })?, ReceiptMismatchInfo::DomainExtrinsicsRoot { .. } => self .fraud_proof_generator - .generate_invalid_domain_extrinsics_root_proof::( + .generate_invalid_domain_extrinsics_root_proof::( self.domain_id, &local_receipt, bad_receipt_hash, diff --git a/domains/client/domain-operator/src/domain_worker_starter.rs b/domains/client/domain-operator/src/domain_worker_starter.rs index 62930c253b..da12a447e4 100644 --- a/domains/client/domain-operator/src/domain_worker_starter.rs +++ b/domains/client/domain-operator/src/domain_worker_starter.rs @@ -89,6 +89,7 @@ pub(super) async fn start_worker< + sp_api::ApiExt, CClient: HeaderBackend + HeaderMetadata + + ProofProvider + BlockBackend + ProofProvider + ProvideRuntimeApi diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index f07ec09286..961a6caca4 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -1,5 +1,5 @@ use crate::aux_schema::InvalidBundlesMismatchType; -use crate::utils::to_number_primitive; +use crate::utils::{to_number_primitive, FraudProofOf}; use crate::ExecutionReceiptFor; use codec::{Decode, Encode}; use domain_block_builder::{BlockBuilder, RecordProof}; @@ -11,16 +11,19 @@ use sp_blockchain::HeaderBackend; use sp_core::traits::CodeExecutor; use sp_core::H256; use sp_domains::fraud_proof::{ - ExecutionPhase, ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, + ConsensusBlockDetails, ExecutionPhase, ExtrinsicDigest, FraudProof, InvalidBundlesFraudProof, InvalidExtrinsicsRootProof, InvalidStateTransitionProof, InvalidTotalRewardsProof, - MissingInvalidBundleEntryFraudProof, ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, + MissingBundleAdditionalData, MissingInvalidBundleEntryFraudProof, + ValidAsInvalidBundleEntryFraudProof, ValidBundleDigest, }; -use sp_domains::{DomainId, DomainsApi}; +use sp_domains::storage_proof::{DomainRuntimeCodeWithProof, OpaqueBundleWithProof}; +use sp_domains::{DomainId, DomainsApi, ExecutionReceipt, InvalidBundleType}; use sp_runtime::traits::{BlakeTwo256, Block as BlockT, HashingFor, Header as HeaderT, NumberFor}; use sp_runtime::Digest; use sp_trie::{LayoutV1, StorageProof}; use std::marker::PhantomData; use std::sync::Arc; +use subspace_core_primitives::U256; use subspace_fraud_proof::invalid_state_transition_proof::ExecutionProver; /// Error type for fraud proof generation. @@ -32,12 +35,26 @@ pub enum FraudProofError { InvalidTraceIndex { index: usize, max: usize }, #[error("Invalid extrinsic index for creating the execution proof, got: {index}, max: {max}")] InvalidExtrinsicIndex { index: usize, max: usize }, + #[error( + "Invalid bundle index for creating the invalid bundles proof, got: {index}, max: {max}" + )] + InvalidBundleIndex { index: usize, max: usize }, + #[error( + "Missing bundle for creating the invalid bundles proof, for: {index}, max: {max_bundles}" + )] + MissingBundle { index: usize, max_bundles: usize }, + #[error("No tx in the bundle with index {bundle_index} is out of range")] + NoOutOfRangeTx { bundle_index: usize }, + #[error("Unable to decode extrinsic with index {extrinsic_index} in bundle {bundle_index}")] + UnableToDecodeExtrinsic { + extrinsic_index: usize, + bundle_index: usize, + decoding_error: codec::Error, + }, #[error(transparent)] Blockchain(#[from] sp_blockchain::Error), #[error(transparent)] RuntimeApi(#[from] sp_api::ApiError), - #[error("Missing bundle at {bundle_index}")] - MissingBundle { bundle_index: usize }, #[error("Invalid bundle extrinsic at bundle index[{bundle_index}]")] InvalidBundleExtrinsic { bundle_index: usize }, } @@ -106,7 +123,7 @@ where domain_id: DomainId, local_receipt: &ExecutionReceiptFor, bad_receipt_hash: H256, - ) -> Result, PCB::Hash>, FraudProofError> + ) -> Result, FraudProofError> where PCB: BlockT, { @@ -122,17 +139,135 @@ where })) } - pub(crate) fn generate_invalid_bundle_field_proof( + fn generate_out_of_range_tx_in_bundle_fraud_proof( &self, domain_id: DomainId, - _local_receipt: &ExecutionReceiptFor, + local_receipt: &ExecutionReceiptFor, + bundle_index: u32, + bad_receipt_hash: H256, + consensus_block_hash: &CBlock::Hash, + runtime_code_with_proof: DomainRuntimeCodeWithProof, + ) -> Result, FraudProofError> { + let out_of_range_bundle = { + let extrinsics = self + .consensus_client + .block_body(*consensus_block_hash)? + .ok_or_else(|| { + sp_blockchain::Error::Backend(format!( + "BlockBody of {consensus_block_hash:?} unavailable" + )) + })?; + let bundles = self + .consensus_client + .runtime_api() + .extract_successful_bundles(*consensus_block_hash, domain_id, extrinsics)?; + bundles + .get(bundle_index as usize) + .ok_or(FraudProofError::MissingBundle { + index: bundle_index as usize, + max_bundles: bundles.len(), + })? + .clone() + }; + + let tx_range = self + .consensus_client + .runtime_api() + .domain_tx_range(local_receipt.consensus_block_hash, domain_id)?; + + let bundle_vrf_hash = U256::from_be_bytes( + out_of_range_bundle + .sealed_header + .header + .proof_of_election + .vrf_hash(), + ); + + let parent_domain_block_hash = { + let header = self + .client + .header(local_receipt.domain_block_hash)? + .ok_or_else(|| { + sp_blockchain::Error::Backend(format!( + "Header of {:?} unavailable", + local_receipt.domain_block_hash + )) + })?; + *header.parent_hash() + }; + + let mut extrinsic_index = None; + for (index, opaque_extrinsic) in out_of_range_bundle.extrinsics.iter().enumerate() { + // TODO: Handle the error + let extrinsic = + <::Extrinsic>::decode(&mut opaque_extrinsic.encode().as_slice()) + .map_err(|e| FraudProofError::UnableToDecodeExtrinsic { + extrinsic_index: index, + bundle_index: bundle_index as usize, + decoding_error: e, + })?; + let is_within_tx_range = self.client.runtime_api().is_within_tx_range( + parent_domain_block_hash, + &extrinsic, + &bundle_vrf_hash, + &tx_range, + )?; + if !is_within_tx_range { + extrinsic_index = Some(index as u32); + break; + } + } + + if extrinsic_index.is_none() { + return Err(FraudProofError::NoOutOfRangeTx { + bundle_index: bundle_index as usize, + }); + } + + let bundle_with_proof = OpaqueBundleWithProof::generate( + self.consensus_client.as_ref(), + domain_id, + *consensus_block_hash, + out_of_range_bundle, + 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, + ConsensusBlockDetails { + consensus_block_incl_bundle: *consensus_block_hash, + consensus_block_incl_er: consensus_block_hash_incl_er, + }, + bundle_index, + bundle_with_proof, + runtime_code_with_proof, + MissingBundleAdditionalData::OutOfRangeTx { + extrinsic_index: extrinsic_index.expect("checked for None above; qed"), + }, + ), + ), + )) + } + + pub fn generate_invalid_bundle_field_proof( + &self, + domain_id: DomainId, + local_receipt: &ExecutionReceiptFor, mismatch_type: InvalidBundlesMismatchType, bundle_index: u32, - _bad_receipt_hash: H256, - ) -> Result, PCB::Hash>, FraudProofError> - where - PCB: BlockT, - { + bad_receipt_hash: H256, + ) -> Result, FraudProofError> { + let ExecutionReceipt { + consensus_block_hash, + invalid_bundles, + .. + } = local_receipt; + match mismatch_type { // TODO: Generate a proper proof once fields are in place InvalidBundlesMismatchType::ValidAsInvalid => Ok(FraudProof::InvalidBundles( @@ -141,12 +276,65 @@ where bundle_index, )), )), - // TODO: Generate a proper proof once fields are in place - InvalidBundlesMismatchType::InvalidAsValid => Ok(FraudProof::InvalidBundles( - InvalidBundlesFraudProof::MissingInvalidBundleEntry( - MissingInvalidBundleEntryFraudProof::new(domain_id, bundle_index), - ), - )), + InvalidBundlesMismatchType::InvalidAsValid => { + let invalid_bundle = invalid_bundles + .iter() + .find(|b| b.bundle_index == bundle_index) + .ok_or(FraudProofError::InvalidBundleIndex { + index: bundle_index as usize, + max: local_receipt.invalid_bundles.len(), + })?; + + let parent_consensus_block_hash = { + let header = self + .consensus_client + .header(*consensus_block_hash)? + .ok_or_else(|| { + sp_blockchain::Error::Backend(format!( + "Header of {consensus_block_hash:?} unavailable" + )) + })?; + *header.parent_hash() + }; + + let runtime_id = self + .consensus_client + .runtime_api() + .runtime_id(parent_consensus_block_hash, domain_id)? + .ok_or_else(|| { + sp_blockchain::Error::Application( + format!( + "runtime_id of {domain_id:?} not found, this should not happen" + ) + .into(), + ) + })?; + // NOTE: we use the parent consensus block here, see the comment of `DomainRuntimeCodeWithProof` + // for more detail. + let runtime_code_with_proof = DomainRuntimeCodeWithProof::generate( + self.consensus_client.as_ref(), + domain_id, + runtime_id, + parent_consensus_block_hash, + )?; + + match invalid_bundle.invalid_bundle_type { + InvalidBundleType::OutOfRangeTx => self + .generate_out_of_range_tx_in_bundle_fraud_proof( + domain_id, + local_receipt, + bundle_index, + bad_receipt_hash, + consensus_block_hash, + runtime_code_with_proof, + ), + // TODO: Add fraud proof for illegal tx + _ => Ok(FraudProof::Dummy { + domain_id, + bad_receipt_hash: Default::default(), + }), + } + } } } @@ -155,7 +343,7 @@ where domain_id: DomainId, local_receipt: &ExecutionReceiptFor, bad_receipt_hash: H256, - ) -> Result, PCB::Hash>, FraudProofError> + ) -> Result, FraudProofError> where PCB: BlockT, { @@ -182,7 +370,8 @@ where bundles .get(bundle_index as usize) .ok_or(FraudProofError::MissingBundle { - bundle_index: bundle_index as usize, + index: bundle_index as usize, + max_bundles: local_receipt.valid_bundles.len(), })?; let mut exts = Vec::with_capacity(bundle.extrinsics.len()); @@ -245,7 +434,7 @@ where local_trace_index: u32, local_receipt: &ExecutionReceiptFor, bad_receipt_hash: H256, - ) -> Result, PCB::Hash>, FraudProofError> + ) -> Result, FraudProofError> where PCB: BlockT, { diff --git a/domains/client/domain-operator/src/lib.rs b/domains/client/domain-operator/src/lib.rs index a698694c4f..689470b086 100644 --- a/domains/client/domain-operator/src/lib.rs +++ b/domains/client/domain-operator/src/lib.rs @@ -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; @@ -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)] diff --git a/domains/client/domain-operator/src/operator.rs b/domains/client/domain-operator/src/operator.rs index 95c56ae8d6..76a5bf0bef 100644 --- a/domains/client/domain-operator/src/operator.rs +++ b/domains/client/domain-operator/src/operator.rs @@ -83,6 +83,7 @@ where + ProvideRuntimeApi + ProofProvider + BlockchainEvents + + ProofProvider + Send + Sync + 'static, diff --git a/domains/client/domain-operator/src/parent_chain.rs b/domains/client/domain-operator/src/parent_chain.rs index 74baa9d3fc..e2cc874605 100644 --- a/domains/client/domain-operator/src/parent_chain.rs +++ b/domains/client/domain-operator/src/parent_chain.rs @@ -8,8 +8,12 @@ use sp_runtime::traits::Block as BlockT; use std::marker::PhantomData; use std::sync::Arc; -type FraudProofFor = - FraudProof, ::Hash>; +type FraudProofFor = FraudProof< + NumberFor, + ::Hash, + NumberFor, + ::Hash, +>; /// Trait for interacting between the domain and its corresponding parent chain, i.e. retrieving /// the necessary info from the parent chain or submit extrinsics to the parent chain. @@ -51,11 +55,11 @@ pub trait ParentChainInterface { &self, at: ParentChainBlock::Hash, extrinsics: Vec, - ) -> Result>, sp_api::ApiError>; + ) -> Result>, sp_api::ApiError>; fn submit_fraud_proof_unsigned( &self, - fraud_proof: FraudProof, ParentChainBlock::Hash>, + fraud_proof: FraudProofFor, ) -> Result<(), sp_api::ApiError>; fn non_empty_er_exists( @@ -172,7 +176,7 @@ where &self, _at: CBlock::Hash, _extrinsics: Vec, - ) -> Result>, sp_api::ApiError> { + ) -> Result>, sp_api::ApiError> { // TODO: Implement when proceeding to fraud proof v2. Ok(Vec::new()) // self.consensus_client @@ -182,7 +186,7 @@ where fn submit_fraud_proof_unsigned( &self, - _fraud_proof: FraudProof, CBlock::Hash>, + _fraud_proof: FraudProofFor, ) -> Result<(), sp_api::ApiError> { // TODO: Implement when proceeding to fraud proof v2. // let at = self.consensus_client.info().best_hash; diff --git a/domains/client/domain-operator/src/utils.rs b/domains/client/domain-operator/src/utils.rs index 262cbb7bfe..ec2e41169b 100644 --- a/domains/client/domain-operator/src/utils.rs +++ b/domains/client/domain-operator/src/utils.rs @@ -1,6 +1,7 @@ use parking_lot::Mutex; use sc_utils::mpsc::{TracingUnboundedReceiver, TracingUnboundedSender}; use sp_consensus_slots::Slot; +use sp_domains::fraud_proof::FraudProof; use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::convert::TryInto; use std::sync::Arc; @@ -46,6 +47,13 @@ pub type DomainImportNotificationSinks = pub type DomainImportNotifications = TracingUnboundedReceiver>; +pub type FraudProofOf = FraudProof< + NumberFor, + ::Hash, + NumberFor, + ::Hash, +>; + #[derive(Clone, Debug)] pub struct DomainBlockImportNotification { pub domain_block_hash: Block::Hash, diff --git a/test/subspace-test-client/Cargo.toml b/test/subspace-test-client/Cargo.toml index cd39769e3d..0fab965aec 100644 --- a/test/subspace-test-client/Cargo.toml +++ b/test/subspace-test-client/Cargo.toml @@ -15,6 +15,7 @@ include = [ targets = ["x86_64-unknown-linux-gnu"] [dependencies] +domain-runtime-primitives = { version = "0.1.0", path = "../../domains/primitives/runtime" } evm-domain-test-runtime = { version = "0.1.0", path = "../../domains/test/runtime/evm" } fp-evm = { version = "3.0.0-dev", git = "https://github.com/subspace/frontier", rev = "04f06279e7ec0b095b82be35f41d09d36f3290f5" } futures = "0.3.28" diff --git a/test/subspace-test-client/src/lib.rs b/test/subspace-test-client/src/lib.rs index c9647a0e4b..e8e0e131d6 100644 --- a/test/subspace-test-client/src/lib.rs +++ b/test/subspace-test-client/src/lib.rs @@ -21,6 +21,7 @@ pub mod chain_spec; pub mod domain_chain_spec; +use domain_runtime_primitives::opaque::Block as DomainBlock; use futures::executor::block_on; use futures::StreamExt; use sc_client_api::{BlockBackend, HeaderBackend}; @@ -78,7 +79,7 @@ pub type Backend = sc_service::TFullBackend; pub type FraudProofVerifier = subspace_service::FraudProofVerifier; -type TxPreValidator = ConsensusChainTxPreValidator; +type TxPreValidator = ConsensusChainTxPreValidator; /// Run a farmer. pub fn start_farmer(new_full: &NewFull) diff --git a/test/subspace-test-runtime/Cargo.toml b/test/subspace-test-runtime/Cargo.toml index 709954bd89..9358d52a45 100644 --- a/test/subspace-test-runtime/Cargo.toml +++ b/test/subspace-test-runtime/Cargo.toml @@ -22,6 +22,7 @@ frame-executive = { version = "4.0.0-dev", default-features = false, git = "http frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } hex-literal = { version = "0.4.0", optional = true } +log = { version = "0.4.20", default-features = false } orml-vesting = { version = "0.4.1-dev", default-features = false, path = "../../orml/vesting" } pallet-balances = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "c90d6edfd8c63168eff0dce6f2ace4d66dd139f7" } pallet-domains = { version = "0.1.0", default-features = false, path = "../../crates/pallet-domains" } @@ -75,6 +76,7 @@ std = [ "frame-support/std", "frame-system/std", "frame-system-rpc-runtime-api/std", + "log/std", "orml-vesting/std", "pallet-balances/std", "pallet-domains/std", diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 83b86ea36d..65a0b16cba 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -19,10 +19,14 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +pub mod test_runtime_extension; + // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +use crate::test_runtime_extension::pallet_test_override; +use crate::test_runtime_extension::runtime_decl_for_test_override_api::TestOverrideApi; use codec::{Compact, CompactLen, Decode, Encode, MaxEncodedLen}; use core::num::NonZeroU64; use domain_runtime_primitives::{ @@ -59,7 +63,7 @@ use sp_domains::fraud_proof::FraudProof; use sp_domains::transaction::PreValidationObject; use sp_domains::{ DomainId, DomainInstanceData, DomainsHoldIdentifier, ExecutionReceipt, OpaqueBundle, - OpaqueBundles, OperatorId, OperatorPublicKey, StakingHoldIdentifier, + OpaqueBundles, OperatorId, OperatorPublicKey, ReceiptHash, StakingHoldIdentifier, }; use sp_messenger::endpoint::{Endpoint, EndpointHandler as EndpointHandlerT, EndpointId}; use sp_messenger::messages::{ @@ -795,6 +799,8 @@ impl orml_vesting::Config for Runtime { type BlockNumberProvider = System; } +impl pallet_test_override::Config for Runtime {} + construct_runtime!( pub struct Runtime { System: frame_system = 0, @@ -823,6 +829,7 @@ construct_runtime!( // Reserve some room for other pallets as we'll remove sudo pallet eventually. Sudo: pallet_sudo = 100, + TestOverride: pallet_test_override = 101, } ); @@ -1111,7 +1118,7 @@ fn extract_receipts( fn extract_fraud_proofs( extrinsics: Vec, domain_id: DomainId, -) -> Vec, Hash>> { +) -> Vec, Hash, DomainNumber, DomainHash>> { // TODO: Ensure fraud proof extrinsic is infallible. extrinsics .into_iter() @@ -1368,7 +1375,7 @@ impl_runtime_apis! { } fn domain_tx_range(_: DomainId) -> U256 { - U256::MAX + >::overriden_tx_range() } fn genesis_state_root(domain_id: DomainId) -> Option { @@ -1422,6 +1429,12 @@ impl_runtime_apis! { } } + impl sp_domains::fraud_proof::ExecutionReceiptApi for Runtime { + fn get_execution_receipt_by_hash(hash: ReceiptHash) -> Option, ::Hash, DomainNumber, DomainHash, Balance>> { + Domains::execution_receipt_by_hash(hash) + } + } + impl sp_session::SessionKeys for Runtime { fn generate_session_keys(seed: Option>) -> Vec { SessionKeys::generate(seed) @@ -1506,4 +1519,10 @@ impl_runtime_apis! { Messenger::should_relay_inbox_message_response(dst_chain_id, msg_id) } } + + impl test_runtime_extension::TestOverrideApi for Runtime { + fn overriden_tx_range() -> U256 { + TestOverride::overridden_tx_range() + } + } } diff --git a/test/subspace-test-runtime/src/test_runtime_extension.rs b/test/subspace-test-runtime/src/test_runtime_extension.rs new file mode 100644 index 0000000000..ca51079bd2 --- /dev/null +++ b/test/subspace-test-runtime/src/test_runtime_extension.rs @@ -0,0 +1,90 @@ +use subspace_core_primitives::U256; + +sp_api::decl_runtime_apis! { + pub trait TestOverrideApi { + fn overriden_tx_range() -> U256; + } +} + +pub mod pallet_test_override { + use frame_system::offchain::SubmitTransaction; + pub use pallet::*; + + #[frame_support::pallet(dev_mode)] + mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + pub use subspace_core_primitives::U256; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::storage] + pub(super) type OveriddenTxRange = StorageValue<_, U256>; + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(0)] + pub fn override_tx_range(origin: OriginFor, new_range: U256) -> DispatchResult { + ensure_none(origin)?; + OveriddenTxRange::::put(new_range); + Ok(()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { + match call { + Call::override_tx_range { new_range: _ } => Ok(()), + _ => Err(InvalidTransaction::Call.into()), + } + } + + fn validate_unsigned( + _source: TransactionSource, + call: &Self::Call, + ) -> TransactionValidity { + match call { + Call::override_tx_range { new_range } => { + ValidTransaction::with_tag_prefix("TestOverrideTxValue") + .priority(TransactionPriority::MAX) + .longevity(TransactionLongevity::MAX) + .and_provides(new_range) + .propagate(true) + .build() + } + + _ => InvalidTransaction::Call.into(), + } + } + } + } + + impl>> Pallet { + pub fn overridden_tx_range() -> U256 { + OveriddenTxRange::::get().unwrap_or(U256::MAX) + } + + pub fn overridden_tx_range_unsigned(new_range: U256) { + let call = Call::override_tx_range { new_range }; + + match SubmitTransaction::>::submit_unsigned_transaction(call.into()) { + Ok(()) => { + log::info!( + target: "subspace_test_runtime::pallet_test_override", + "Submitted unsigned tx to override tx range to value: {new_range}", + ); + } + Err(()) => { + log::error!(target: "subspace_test_runtime::pallet_test_override", "Error submitting override tx value"); + } + } + } + } +} diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index ed321bd25f..937198f4f7 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -20,6 +20,7 @@ use codec::{Decode, Encode}; use cross_domain_message_gossip::GossipWorkerBuilder; +use domain_runtime_primitives::opaque::Block as DomainBlock; use domain_runtime_primitives::BlockNumber as DomainNumber; use futures::channel::mpsc; use futures::{select, FutureExt, StreamExt}; @@ -71,6 +72,7 @@ use std::marker::PhantomData; use std::sync::Arc; use std::time; use subspace_core_primitives::{Randomness, Solution}; +use subspace_fraud_proof::invalid_bundles_fraud_proof::InvalidBundleProofVerifier; use subspace_fraud_proof::invalid_state_transition_proof::InvalidStateTransitionProofVerifier; use subspace_fraud_proof::invalid_transaction_proof::InvalidTransactionProofVerifier; use subspace_fraud_proof::verifier_api::VerifierClient; @@ -173,7 +175,7 @@ pub fn node_config( type StorageChanges = sp_api::StorageChanges; -type TxPreValidator = ConsensusChainTxPreValidator; +type TxPreValidator = ConsensusChainTxPreValidator; struct MockExtensionsFactory(Arc); @@ -223,7 +225,7 @@ pub struct MockConsensusNode { /// Block import pipeline #[allow(clippy::type_complexity)] block_import: MockBlockImport< - FraudProofBlockImport, FraudProofVerifier, DomainNumber, H256>, + FraudProofBlockImport, FraudProofVerifier>, Client, Block, >, @@ -277,6 +279,9 @@ impl MockConsensusNode { VerifierClient::new(client.clone()), ); + let invalid_bundle_proof_verifier = + InvalidBundleProofVerifier::new(client.clone(), Arc::new(executor.clone())); + let invalid_state_transition_proof_verifier = InvalidStateTransitionProofVerifier::new( client.clone(), executor.clone(), @@ -286,6 +291,7 @@ impl MockConsensusNode { let proof_verifier = subspace_fraud_proof::ProofVerifier::new( Arc::new(invalid_transaction_proof_verifier), Arc::new(invalid_state_transition_proof_verifier), + Arc::new(invalid_bundle_proof_verifier), ); let tx_pre_validator = ConsensusChainTxPreValidator::new(