diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 095e4ab887..78aff02bd2 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -279,6 +279,7 @@ pub(crate) struct MockDomainFraudProofExtension { runtime_code: Vec, tx_range: bool, is_inherent: bool, + is_decodable: bool, domain_total_stake: Balance, bundle_slot_probability: (u64, u64), operator_stake: Balance, @@ -331,6 +332,9 @@ impl FraudProofHostFunctions for MockDomainFraudProofExtension { FraudProofVerificationInfoRequest::InherentExtrinsicCheck { .. } => { FraudProofVerificationInfoResponse::InherentExtrinsicCheck(self.is_inherent) } + FraudProofVerificationInfoRequest::ExtrinsicDecodableCheck { .. } => { + FraudProofVerificationInfoResponse::ExtrinsicDecodableCheck(self.is_decodable) + } FraudProofVerificationInfoRequest::DomainElectionParams { .. } => { FraudProofVerificationInfoResponse::DomainElectionParams { domain_total_stake: self.domain_total_stake, @@ -1032,6 +1036,7 @@ fn test_invalid_domain_extrinsic_root_proof() { runtime_code: vec![1, 2, 3, 4], tx_range: true, is_inherent: true, + is_decodable: true, domain_total_stake: 100 * SSC, operator_stake: 10 * SSC, bundle_slot_probability: (0, 0), @@ -1112,6 +1117,7 @@ fn test_true_invalid_bundles_inherent_extrinsic_proof() { tx_range: true, // return `true` indicating this is an inherent extrinsic is_inherent: true, + is_decodable: true, domain_total_stake: 100 * SSC, operator_stake: 10 * SSC, bundle_slot_probability: (0, 0), @@ -1178,6 +1184,7 @@ fn test_false_invalid_bundles_inherent_extrinsic_proof() { tx_range: true, // return `false` indicating this is not an inherent extrinsic is_inherent: false, + is_decodable: true, domain_total_stake: 100 * SSC, operator_stake: 10 * SSC, bundle_slot_probability: (0, 0), diff --git a/crates/sp-domains-fraud-proof/src/fraud_proof.rs b/crates/sp-domains-fraud-proof/src/fraud_proof.rs index 8a8fb5a10a..01abfddcc6 100644 --- a/crates/sp-domains-fraud-proof/src/fraud_proof.rs +++ b/crates/sp-domains-fraud-proof/src/fraud_proof.rs @@ -305,6 +305,12 @@ pub enum VerificationError { error("Failed to check if a given extrinsic is inherent or not") )] FailedToCheckInherentExtrinsic, + /// Failed to check if a given extrinsic is decodable or not. + #[cfg_attr( + feature = "thiserror", + error("Failed to check if a given extrinsic is decodable or not") + )] + FailedToCheckExtrinsicDecodable, /// Invalid bundle equivocation fraud proof. #[cfg_attr( feature = "thiserror", diff --git a/crates/sp-domains-fraud-proof/src/host_functions.rs b/crates/sp-domains-fraud-proof/src/host_functions.rs index d887c36c3f..df19809ce1 100644 --- a/crates/sp-domains-fraud-proof/src/host_functions.rs +++ b/crates/sp-domains-fraud-proof/src/host_functions.rs @@ -261,6 +261,22 @@ where .ok() } + fn is_decodable_extrinsic( + &self, + consensus_block_hash: H256, + domain_id: DomainId, + opaque_extrinsic: OpaqueExtrinsic, + ) -> Option { + let runtime_code = self.get_domain_runtime_code(consensus_block_hash, domain_id)?; + let domain_stateless_runtime = + StatelessRuntime::::new(self.executor.clone(), runtime_code.into()); + + Some(matches!( + domain_stateless_runtime.decode_extrinsic(opaque_extrinsic), + Ok(Ok(_)) + )) + } + fn get_domain_election_params( &self, consensus_block_hash: H256, @@ -390,6 +406,14 @@ where .map(|is_inherent| { FraudProofVerificationInfoResponse::InherentExtrinsicCheck(is_inherent) }), + FraudProofVerificationInfoRequest::ExtrinsicDecodableCheck { + domain_id, + opaque_extrinsic, + } => self + .is_decodable_extrinsic(consensus_block_hash, domain_id, opaque_extrinsic) + .map(|is_decodable| { + FraudProofVerificationInfoResponse::ExtrinsicDecodableCheck(is_decodable) + }), FraudProofVerificationInfoRequest::DomainElectionParams { domain_id } => self .get_domain_election_params(consensus_block_hash, domain_id) .map(|domain_election_params| { diff --git a/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index 059dbd0f80..1b417884b0 100644 --- a/crates/sp-domains-fraud-proof/src/lib.rs +++ b/crates/sp-domains-fraud-proof/src/lib.rs @@ -110,6 +110,12 @@ pub enum FraudProofVerificationInfoRequest { /// Extrinsic for which we need to if it is inherent or not. opaque_extrinsic: OpaqueExtrinsic, }, + /// Request to check if the domain extrinsic is decodable or not. + ExtrinsicDecodableCheck { + domain_id: DomainId, + /// Extrinsic for which we need to if it is decodable or not. + opaque_extrinsic: OpaqueExtrinsic, + }, /// Request to get Domain election params. DomainElectionParams { domain_id: DomainId }, /// Request to get Operator stake. @@ -160,6 +166,8 @@ pub enum FraudProofVerificationInfoResponse { TxRangeCheck(bool), /// If the particular extrinsic provided is either inherent or not. InherentExtrinsicCheck(bool), + /// If the domain extrinsic is decodable or not. + ExtrinsicDecodableCheck(bool), /// Domain's total stake at a given Consensus hash. DomainElectionParams { domain_total_stake: Balance, @@ -227,6 +235,15 @@ impl FraudProofVerificationInfoResponse { } } + pub fn into_extrinsic_decodable_check(self) -> Option { + match self { + FraudProofVerificationInfoResponse::ExtrinsicDecodableCheck(is_decodable) => { + Some(is_decodable) + } + _ => None, + } + } + pub fn into_domain_election_params(self) -> Option<(Balance, (u64, u64))> { match self { FraudProofVerificationInfoResponse::DomainElectionParams { diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index 704e9eef93..0fa68e42ab 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -522,6 +522,27 @@ where Err(VerificationError::InvalidProof) } } + InvalidBundleType::UndecodableTx(extrinsic_index) => { + let extrinsic = get_extrinsic_from_proof::( + *extrinsic_index, + invalid_bundle_entry.extrinsics_root, + invalid_bundles_fraud_proof.proof_data.clone(), + )?; + let is_decodable = get_fraud_proof_verification_info( + H256::from_slice(bad_receipt.consensus_block_hash.as_ref()), + FraudProofVerificationInfoRequest::ExtrinsicDecodableCheck { + domain_id: invalid_bundles_fraud_proof.domain_id, + opaque_extrinsic: extrinsic, + }, + ) + .and_then(FraudProofVerificationInfoResponse::into_extrinsic_decodable_check) + .ok_or(VerificationError::FailedToCheckExtrinsicDecodable)?; + + if is_decodable == invalid_bundles_fraud_proof.is_true_invalid_fraud_proof { + return Err(VerificationError::InvalidProof); + } + Ok(()) + } // TODO: implement the other invalid bundle types _ => Err(VerificationError::InvalidProof), diff --git a/domains/client/block-preprocessor/src/lib.rs b/domains/client/block-preprocessor/src/lib.rs index 76930aaf08..5043080a3f 100644 --- a/domains/client/block-preprocessor/src/lib.rs +++ b/domains/client/block-preprocessor/src/lib.rs @@ -16,7 +16,7 @@ pub mod xdm_verifier; use crate::inherents::is_runtime_upgraded; use crate::xdm_verifier::is_valid_xdm; -use codec::{Decode, Encode}; +use codec::Encode; use domain_runtime_primitives::opaque::AccountId; use domain_runtime_primitives::DomainCoreApi; use sc_client_api::BlockBackend; @@ -263,17 +263,20 @@ where // NOTE: for each extrinsic the checking order must follow `InvalidBundleType::checking_order` let runtime_api = self.client.runtime_api(); for (index, opaque_extrinsic) in bundle.extrinsics.iter().enumerate() { - let Ok(extrinsic) = - <::Extrinsic>::decode(&mut opaque_extrinsic.encode().as_slice()) - else { - tracing::error!( - ?opaque_extrinsic, - "Undecodable extrinsic in bundle({})", - bundle.hash() - ); - return Ok(BundleValidity::Invalid(InvalidBundleType::UndecodableTx( - index as u32, - ))); + let decode_result = runtime_api.decode_extrinsic(at, opaque_extrinsic.clone())?; + let extrinsic = match decode_result { + Ok(extrinsic) => extrinsic, + Err(err) => { + tracing::error!( + ?opaque_extrinsic, + ?err, + "Undecodable extrinsic in bundle({})", + bundle.hash() + ); + return Ok(BundleValidity::Invalid(InvalidBundleType::UndecodableTx( + index as u32, + ))); + } }; let is_within_tx_range = diff --git a/domains/client/block-preprocessor/src/stateless_runtime.rs b/domains/client/block-preprocessor/src/stateless_runtime.rs index 38592cbf90..e653c5d22d 100644 --- a/domains/client/block-preprocessor/src/stateless_runtime.rs +++ b/domains/client/block-preprocessor/src/stateless_runtime.rs @@ -1,6 +1,8 @@ use codec::{Codec, Encode}; use domain_runtime_primitives::opaque::AccountId; -use domain_runtime_primitives::{CheckExtrinsicsValidityError, DomainCoreApi}; +use domain_runtime_primitives::{ + CheckExtrinsicsValidityError, DecodeExtrinsicError, DomainCoreApi, +}; use sc_executor::RuntimeVersionOf; use sp_api::{ApiError, BlockT, Core, Hasher, RuntimeVersion}; use sp_core::traits::{CallContext, CodeExecutor, FetchRuntimeCode, RuntimeCode}; @@ -197,6 +199,13 @@ where >::is_inherent_extrinsic(self, Default::default(), extrinsic) } + pub fn decode_extrinsic( + &self, + opaque_extrinsic: sp_runtime::OpaqueExtrinsic, + ) -> Result::Extrinsic, DecodeExtrinsicError>, ApiError> { + >::decode_extrinsic(self, Default::default(), opaque_extrinsic) + } + pub fn is_within_tx_range( &self, extrinsic: &::Extrinsic, diff --git a/domains/primitives/runtime/src/lib.rs b/domains/primitives/runtime/src/lib.rs index ae3312c8c5..9793a801c5 100644 --- a/domains/primitives/runtime/src/lib.rs +++ b/domains/primitives/runtime/src/lib.rs @@ -17,6 +17,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + +use alloc::string::String; use frame_support::dispatch::{DispatchClass, PerDispatchClass}; use frame_support::weights::constants::{BlockExecutionWeight, ExtrinsicBaseWeight}; use frame_system::limits::{BlockLength, BlockWeights}; @@ -168,6 +171,9 @@ pub struct CheckExtrinsicsValidityError { pub transaction_validity_error: TransactionValidityError, } +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] +pub struct DecodeExtrinsicError(pub String); + /// fullu qualified method name of check_extrinsics_and_do_pre_dispatch runtime api. /// Used to call state machine. /// Change it when the runtime api's name is changed in the interface. @@ -233,6 +239,11 @@ sp_api::decl_runtime_apis! { fn check_extrinsics_and_do_pre_dispatch(uxts: Vec<::Extrinsic>, block_number: NumberFor, block_hash: ::Hash) -> Result<(), CheckExtrinsicsValidityError>; + /// Decodes the domain specific extrinsic from the opaque extrinsic. + fn decode_extrinsic( + opaque_extrinsic: sp_runtime::OpaqueExtrinsic, + ) -> Result<::Extrinsic, DecodeExtrinsicError>; + /// Returns extrinsic Era if present fn extrinsic_era( extrinsic: &::Extrinsic diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index 7a18d88c20..826f8b9a65 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -8,6 +8,8 @@ mod precompiles; #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +extern crate alloc; + use codec::{Decode, Encode}; use domain_runtime_primitives::opaque::Header; pub use domain_runtime_primitives::{ @@ -15,7 +17,8 @@ pub use domain_runtime_primitives::{ EXISTENTIAL_DEPOSIT, MAXIMUM_BLOCK_WEIGHT, }; use domain_runtime_primitives::{ - CheckExtrinsicsValidityError, MultiAccountId, TryConvertBack, SLOT_DURATION, + CheckExtrinsicsValidityError, DecodeExtrinsicError, MultiAccountId, TryConvertBack, + SLOT_DURATION, }; use fp_account::EthereumSignature; use fp_self_contained::{CheckedSignature, SelfContainedCall}; @@ -951,6 +954,14 @@ impl_runtime_apis! { Ok(()) } + fn decode_extrinsic( + opaque_extrinsic: sp_runtime::OpaqueExtrinsic, + ) -> Result<::Extrinsic, DecodeExtrinsicError> { + let encoded = opaque_extrinsic.encode(); + UncheckedExtrinsic::decode(&mut encoded.as_slice()) + .map_err(|err| DecodeExtrinsicError(alloc::format!("{}", err))) + } + fn extrinsic_era( extrinsic: &::Extrinsic ) -> Option { diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index 1e1030cf6d..91e3a80851 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -8,6 +8,8 @@ mod precompiles; #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +extern crate alloc; + use codec::{Decode, Encode}; pub use domain_runtime_primitives::opaque::Header; use domain_runtime_primitives::{ @@ -15,7 +17,7 @@ use domain_runtime_primitives::{ MAXIMUM_BLOCK_WEIGHT, SLOT_DURATION, }; pub use domain_runtime_primitives::{ - opaque, Balance, BlockNumber, CheckExtrinsicsValidityError, Hash, Nonce, + opaque, Balance, BlockNumber, CheckExtrinsicsValidityError, DecodeExtrinsicError, Hash, Nonce, }; use fp_account::EthereumSignature; use fp_self_contained::{CheckedSignature, SelfContainedCall}; @@ -916,6 +918,14 @@ impl_runtime_apis! { Ok(()) } + fn decode_extrinsic( + opaque_extrinsic: sp_runtime::OpaqueExtrinsic, + ) -> Result<::Extrinsic, DecodeExtrinsicError> { + let encoded = opaque_extrinsic.encode(); + UncheckedExtrinsic::decode(&mut encoded.as_slice()) + .map_err(|err| DecodeExtrinsicError(alloc::format!("{}", err))) + } + fn extrinsic_era( extrinsic: &::Extrinsic ) -> Option {