diff --git a/Cargo.lock b/Cargo.lock index 710136c7137..45f1bd60f96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13857,8 +13857,10 @@ dependencies = [ name = "subspace-runtime-primitives" version = "0.1.0" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", + "pallet-balances", "pallet-multisig", "pallet-transaction-payment", "pallet-utility", diff --git a/crates/pallet-domains/src/extensions.rs b/crates/pallet-domains/src/extensions.rs index 689b0960151..82a76a7fd68 100644 --- a/crates/pallet-domains/src/extensions.rs +++ b/crates/pallet-domains/src/extensions.rs @@ -3,6 +3,7 @@ use crate::pallet::Call as DomainsCall; use crate::weights::WeightInfo; use crate::{Config, FraudProofFor, OpaqueBundleOf, Origin, Pallet as Domains, SingletonReceiptOf}; +use frame_support::ensure; use frame_support::pallet_prelude::{PhantomData, TypeInfo}; use frame_support::weights::Weight; use frame_system::pallet_prelude::RuntimeCallFor; @@ -28,6 +29,12 @@ where fn maybe_domains_call(&self) -> Option<&DomainsCall>; } +/// Trait to check if the Domains are enabled on Consensus. +pub trait DomainsCheck { + /// Check if the domains are enabled on Runtime. + fn is_domains_enabled() -> bool; +} + /// Extensions for pallet-domains unsigned extrinsics. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] pub struct DomainsExtension(PhantomData); @@ -165,7 +172,7 @@ where impl TransactionExtension> for DomainsExtension where - Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync, + Runtime: Config + scale_info::TypeInfo + fmt::Debug + Send + Sync + DomainsCheck, as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<::AccountId> + From + Clone, RuntimeCallFor: MaybeDomainsCall, @@ -177,19 +184,24 @@ where fn weight(&self, call: &Runtime::RuntimeCall) -> Weight { // This extension only apply to the following 3 calls thus only return weight for them. - match call.maybe_domains_call() { + let maybe_weight = match call.maybe_domains_call() { Some(DomainsCall::submit_bundle { .. }) => { - ::WeightInfo::validate_submit_bundle() + Some(::WeightInfo::validate_submit_bundle()) } - Some(DomainsCall::submit_fraud_proof { fraud_proof }) => { + Some(DomainsCall::submit_fraud_proof { fraud_proof }) => Some( ::WeightInfo::fraud_proof_pre_check() - .saturating_add(fraud_proof_verification_weights::<_, _, _, _>(fraud_proof)) - } + .saturating_add(fraud_proof_verification_weights::<_, _, _, _>(fraud_proof)), + ), Some(DomainsCall::submit_receipt { .. }) => { - ::WeightInfo::validate_singleton_receipt() + Some(::WeightInfo::validate_singleton_receipt()) } - _ => Weight::zero(), - } + _ => None, + }; + + // There is one additional runtime read to check if the domains are enabled + maybe_weight + .and_then(|weight| weight.checked_add(&Runtime::DbWeight::get().reads(1))) + .unwrap_or(Runtime::DbWeight::get().reads(1)) } fn validate( @@ -212,6 +224,9 @@ where None => return Ok((ValidTransaction::default(), (), origin)), }; + // ensure domains are enabled + ensure!(Runtime::is_domains_enabled(), InvalidTransaction::Call); + let validity = match domains_call { DomainsCall::submit_bundle { opaque_bundle } => { Self::do_validate_submit_bundle(opaque_bundle, source)? diff --git a/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs b/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs index 418dc66f011..40affac5592 100644 --- a/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs +++ b/crates/subspace-malicious-operator/src/malicious_bundle_producer.rs @@ -28,7 +28,8 @@ use sp_transaction_pool::runtime_api::TaggedTransactionQueue; use std::error::Error; use std::sync::Arc; use subspace_core_primitives::pot::PotOutput; -use subspace_runtime::{DisablePallets, Runtime, RuntimeCall, SignedExtra, UncheckedExtrinsic}; +use subspace_runtime::{Runtime, RuntimeCall, SignedExtra, UncheckedExtrinsic}; +use subspace_runtime_primitives::extension::BalanceTransferCheckExtension; use subspace_runtime_primitives::opaque::Block as CBlock; use subspace_runtime_primitives::{AccountId, Balance, BlockHashFor, HeaderFor, Nonce}; @@ -414,7 +415,7 @@ fn get_singed_extra(best_number: u64, immortal: bool, nonce: Nonce) -> SignedExt frame_system::CheckNonce::::from(nonce.into()), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0u128), - DisablePallets, + BalanceTransferCheckExtension::::default(), pallet_subspace::extensions::SubspaceExtension::::new(), pallet_domains::extensions::DomainsExtension::::new(), pallet_messenger::extensions::MessengerExtension::::new(), diff --git a/crates/subspace-runtime-primitives/Cargo.toml b/crates/subspace-runtime-primitives/Cargo.toml index a5bc9a9a171..0410699d89b 100644 --- a/crates/subspace-runtime-primitives/Cargo.toml +++ b/crates/subspace-runtime-primitives/Cargo.toml @@ -17,8 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] parity-scale-codec = { workspace = true, features = ["derive"] } +frame-benchmarking = { workspace = true, optional = true } frame-support.workspace = true frame-system.workspace = true +pallet-balances.workspace = true pallet-multisig.workspace = true pallet-transaction-payment.workspace = true pallet-utility.workspace = true @@ -35,6 +37,7 @@ std = [ "parity-scale-codec/std", "frame-support/std", "frame-system/std", + "pallet-balances/std", "pallet-multisig/std", "pallet-transaction-payment/std", "pallet-utility/std", @@ -47,3 +50,10 @@ std = [ testing = [ "sp-io" ] + +runtime-benchmarks = [ + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] diff --git a/crates/subspace-runtime-primitives/src/extension.rs b/crates/subspace-runtime-primitives/src/extension.rs new file mode 100644 index 00000000000..7ba3eed821d --- /dev/null +++ b/crates/subspace-runtime-primitives/src/extension.rs @@ -0,0 +1,200 @@ +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; +pub mod weights; + +use crate::extension::weights::WeightInfo as SubstrateWeightInfo; +use crate::utility::{MaybeNestedCall, nested_call_iter}; +use core::marker::PhantomData; +use frame_support::RuntimeDebugNoBound; +use frame_support::pallet_prelude::Weight; +use frame_system::Config; +use frame_system::pallet_prelude::{OriginFor, RuntimeCallFor}; +use pallet_balances::Call as BalancesCall; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use scale_info::prelude::fmt; +use sp_runtime::DispatchResult; +use sp_runtime::traits::{ + AsSystemOriginSigner, DispatchInfoOf, DispatchOriginOf, Dispatchable, PostDispatchInfoOf, + TransactionExtension, ValidateResult, +}; +use sp_runtime::transaction_validity::{ + InvalidTransaction, TransactionSource, TransactionValidityError, ValidTransaction, +}; + +/// Maximum number of calls we benchmarked for. +const MAXIMUM_NUMBER_OF_CALLS: u32 = 5_000; + +/// Weights for the balance transfer check extension. +pub trait WeightInfo { + fn balance_transfer_check_multiple(c: u32) -> Weight; + fn balance_transfer_check_utility(c: u32) -> Weight; + fn balance_transfer_check_multisig(c: u32) -> Weight; +} + +/// Trait to convert Runtime call to possible Balance call. +pub trait MaybeBalancesCall +where + Runtime: pallet_balances::Config, +{ + fn maybe_balance_call(&self) -> Option<&BalancesCall>; +} + +/// Trait to check if the Balance transfers are enabled. +pub trait BalanceTransferChecks { + fn is_balance_transferable() -> bool; +} + +/// Disable balance transfers, if configured in the runtime. +#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +pub struct BalanceTransferCheckExtension(PhantomData); + +impl Default for BalanceTransferCheckExtension +where + Runtime: BalanceTransferChecks + pallet_balances::Config, + RuntimeCallFor: MaybeBalancesCall + MaybeNestedCall, +{ + fn default() -> Self { + Self(PhantomData) + } +} + +impl BalanceTransferCheckExtension +where + Runtime: BalanceTransferChecks + pallet_balances::Config, + RuntimeCallFor: MaybeBalancesCall + MaybeNestedCall, +{ + fn do_validate_signed( + call: &RuntimeCallFor, + ) -> Result<(ValidTransaction, u32), TransactionValidityError> { + if Runtime::is_balance_transferable() { + return Ok((ValidTransaction::default(), 0)); + } + + // Disable normal balance transfers. + let (contains_balance_call, calls) = Self::contains_balance_transfer(call); + if contains_balance_call { + Err(InvalidTransaction::Call.into()) + } else { + Ok((ValidTransaction::default(), calls)) + } + } + + fn contains_balance_transfer(call: &RuntimeCallFor) -> (bool, u32) { + let mut calls = 0; + for call in nested_call_iter::(call) { + calls += 1; + // Any other calls might contain nested calls, so we can only return early if we find a + // balance transfer call. + if let Some(balance_call) = call.maybe_balance_call() + && matches!( + balance_call, + BalancesCall::transfer_allow_death { .. } + | BalancesCall::transfer_keep_alive { .. } + | BalancesCall::transfer_all { .. } + ) + { + return (true, calls); + } + } + + (false, calls) + } + + fn get_weights(n: u32) -> Weight { + SubstrateWeightInfo::::balance_transfer_check_multisig(n) + .max(SubstrateWeightInfo::::balance_transfer_check_multiple(n)) + .max(SubstrateWeightInfo::::balance_transfer_check_utility(n)) + } +} + +/// Data passed from prepare to post_dispatch. +#[derive(RuntimeDebugNoBound)] +pub enum Pre { + Refund(Weight), +} + +/// Data passed from validate to prepare. +#[derive(RuntimeDebugNoBound)] +pub enum Val { + FullRefund, + PartialRefund(Option), +} + +impl TransactionExtension> + for BalanceTransferCheckExtension +where + Runtime: Config + + pallet_balances::Config + + scale_info::TypeInfo + + fmt::Debug + + Send + + Sync + + BalanceTransferChecks, + as Dispatchable>::RuntimeOrigin: + AsSystemOriginSigner<::AccountId> + Clone, + RuntimeCallFor: MaybeBalancesCall + MaybeNestedCall, +{ + const IDENTIFIER: &'static str = "BalanceTransferCheckExtension"; + type Implicit = (); + type Val = Val; + type Pre = Pre; + + fn weight(&self, _call: &RuntimeCallFor) -> Weight { + Self::get_weights(MAXIMUM_NUMBER_OF_CALLS) + } + + fn validate( + &self, + origin: OriginFor, + call: &RuntimeCallFor, + _info: &DispatchInfoOf>, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> ValidateResult> { + let (validity, val) = if origin.as_system_origin_signer().is_some() { + let (valid, maybe_calls) = + Self::do_validate_signed(call).map(|(valid, calls)| (valid, Some(calls)))?; + (valid, Val::PartialRefund(maybe_calls)) + } else { + (ValidTransaction::default(), Val::FullRefund) + }; + + Ok((validity, val, origin)) + } + + fn prepare( + self, + val: Self::Val, + _origin: &DispatchOriginOf>, + _call: &RuntimeCallFor, + _info: &DispatchInfoOf>, + _len: usize, + ) -> Result { + let total_weight = Self::get_weights(MAXIMUM_NUMBER_OF_CALLS); + match val { + // not a signed transaction, so return full refund. + Val::FullRefund => Ok(Pre::Refund(total_weight)), + + // signed transaction with a minimum of one read weight, + // so refund any extra call weight + Val::PartialRefund(maybe_calls) => { + let actual_weights = Self::get_weights(maybe_calls.unwrap_or(0)); + Ok(Pre::Refund(total_weight.saturating_sub(actual_weights))) + } + } + } + + fn post_dispatch_details( + pre: Self::Pre, + _info: &DispatchInfoOf>, + _post_info: &PostDispatchInfoOf>, + _len: usize, + _result: &DispatchResult, + ) -> Result { + let Pre::Refund(weight) = pre; + Ok(weight) + } +} diff --git a/crates/subspace-runtime-primitives/src/extension/benchmarking.rs b/crates/subspace-runtime-primitives/src/extension/benchmarking.rs new file mode 100644 index 00000000000..9f2fbde3d2f --- /dev/null +++ b/crates/subspace-runtime-primitives/src/extension/benchmarking.rs @@ -0,0 +1,137 @@ +//! Benchmarking for `BalanceTransferCheck` extensions. + +use crate::extension::{ + BalanceTransferCheckExtension, BalanceTransferChecks, MAXIMUM_NUMBER_OF_CALLS, + MaybeBalancesCall, MaybeNestedCall, +}; +use core::marker::PhantomData; +use frame_benchmarking::v2::*; +use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; +use frame_system::Config; +use frame_system::pallet_prelude::RuntimeCallFor; +use pallet_balances::{Call as BalancesCall, Config as BalancesConfig}; +use pallet_multisig::{Call as MultisigCall, Config as MultisigConfig}; +use pallet_utility::{Call as UtilityCall, Config as UtilityConfig}; +use scale_info::prelude::boxed::Box; +use scale_info::prelude::vec::Vec; +use scale_info::prelude::{fmt, vec}; +use sp_runtime::Weight; +use sp_runtime::traits::{Dispatchable, StaticLookup}; + +pub struct Pallet(PhantomData); + +const SEED: u32 = 0; + +#[allow(clippy::multiple_bound_locations)] +#[benchmarks(where + T: Send + Sync + scale_info::TypeInfo + fmt::Debug + + UtilityConfig + BalanceTransferChecks + BalancesConfig + MultisigConfig, + RuntimeCallFor: + Dispatchable + + From> + From> + From> + + Into<::RuntimeCall> + + MaybeBalancesCall + MaybeNestedCall) +] +mod benchmarks { + use super::*; + use frame_system::pallet_prelude::RuntimeCallFor; + + #[benchmark] + fn balance_transfer_check_multiple(c: Linear<0, MAXIMUM_NUMBER_OF_CALLS>) { + let mut calls = Vec::with_capacity(c as usize + 1); + for _i in 0..=c { + // Non-balance calls are more expensive to check, because we have to read them all. + // (We can only exit the check loop early if we encounter a balance transfer call.) + calls.push(construct_non_balance_call::()); + } + + let call = construct_utility_call_list::(calls); + + #[block] + { + BalanceTransferCheckExtension::::do_validate_signed(&call).unwrap(); + } + } + + #[benchmark] + fn balance_transfer_check_utility(c: Linear<0, MAXIMUM_NUMBER_OF_CALLS>) { + let mut call = construct_balance_call::(); + for _i in 0..=c { + call = construct_utility_call::(call); + } + #[block] + { + BalanceTransferCheckExtension::::do_validate_signed(&call).unwrap(); + } + } + + #[benchmark] + fn balance_transfer_check_multisig(c: Linear<0, MAXIMUM_NUMBER_OF_CALLS>) { + let mut call = construct_balance_call::(); + for _i in 0..=c { + call = construct_multisig_call::(call); + } + #[block] + { + BalanceTransferCheckExtension::::do_validate_signed(&call).unwrap(); + } + } +} + +fn construct_balance_call() -> RuntimeCallFor +where + RuntimeCallFor: From>, +{ + let recipient: T::AccountId = account("recipient", 0, SEED); + let recipient_lookup = T::Lookup::unlookup(recipient.clone()); + BalancesCall::transfer_all { + dest: recipient_lookup, + keep_alive: true, + } + .into() +} + +fn construct_non_balance_call() -> RuntimeCallFor +where + RuntimeCallFor: From>, +{ + let recipient: T::AccountId = account("recipient", 0, SEED); + BalancesCall::upgrade_accounts { + who: vec![recipient], + } + .into() +} + +fn construct_utility_call(call: RuntimeCallFor) -> RuntimeCallFor +where + RuntimeCallFor: From>, +{ + UtilityCall::batch_all { + calls: vec![call.into()], + } + .into() +} + +fn construct_utility_call_list(calls: Vec>) -> RuntimeCallFor +where + RuntimeCallFor: From>, +{ + UtilityCall::batch_all { + calls: calls.into_iter().map(Into::into).collect(), + } + .into() +} + +fn construct_multisig_call(call: RuntimeCallFor) -> RuntimeCallFor +where + RuntimeCallFor: From> + Into<::RuntimeCall>, +{ + MultisigCall::as_multi { + threshold: 0, + other_signatories: vec![], + maybe_timepoint: None, + call: Box::new(call.into()), + max_weight: Weight::zero(), + } + .into() +} diff --git a/crates/subspace-runtime-primitives/src/extension/weights.rs b/crates/subspace-runtime-primitives/src/extension/weights.rs new file mode 100644 index 00000000000..a4757531ded --- /dev/null +++ b/crates/subspace-runtime-primitives/src/extension/weights.rs @@ -0,0 +1,77 @@ + +//! Autogenerated weights for `balance_transfer_check_extension` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 46.2.0 +//! DATE: 2025-06-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `MacBook-Pro`, CPU: `M1 Max` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/subspace-node +// benchmark +// pallet +// --runtime=./target/release/wbuild/subspace-runtime/subspace_runtime.compact.compressed.wasm +// --genesis-builder=none +// --steps=50 +// --repeat=20 +// --pallet=balance_transfer_check_extension +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./crates/subspace-runtime-primitives/src/extension/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `balance_transfer_check_extension`. +pub struct WeightInfo(PhantomData); +impl crate::extension::WeightInfo for WeightInfo { + /// Storage: `RuntimeConfigs::EnableBalanceTransfers` (r:1 w:0) + /// Proof: `RuntimeConfigs::EnableBalanceTransfers` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 5000]`. + fn balance_transfer_check_multiple(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1486` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_139_820, 0) + .saturating_add(Weight::from_parts(0, 1486)) + // Standard Error: 12 + .saturating_add(Weight::from_parts(189, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `RuntimeConfigs::EnableBalanceTransfers` (r:1 w:0) + /// Proof: `RuntimeConfigs::EnableBalanceTransfers` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 5000]`. + fn balance_transfer_check_utility(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1486` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_024_479, 0) + .saturating_add(Weight::from_parts(0, 1486)) + // Standard Error: 11 + .saturating_add(Weight::from_parts(67, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } + /// Storage: `RuntimeConfigs::EnableBalanceTransfers` (r:1 w:0) + /// Proof: `RuntimeConfigs::EnableBalanceTransfers` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// The range of component `c` is `[0, 5000]`. + fn balance_transfer_check_multisig(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1486` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(968_264, 0) + .saturating_add(Weight::from_parts(0, 1486)) + // Standard Error: 49 + .saturating_add(Weight::from_parts(401, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } +} diff --git a/crates/subspace-runtime-primitives/src/lib.rs b/crates/subspace-runtime-primitives/src/lib.rs index 28aba0ff7c0..bc92cdecbd8 100644 --- a/crates/subspace-runtime-primitives/src/lib.rs +++ b/crates/subspace-runtime-primitives/src/lib.rs @@ -1,7 +1,8 @@ //! Runtime primitives for Subspace Network. - +#![feature(let_chains)] #![cfg_attr(not(feature = "std"), no_std)] +pub mod extension; pub mod utility; #[cfg(not(feature = "std"))] diff --git a/crates/subspace-runtime/Cargo.toml b/crates/subspace-runtime/Cargo.toml index 10cecd7c0b4..698e9ea25c4 100644 --- a/crates/subspace-runtime/Cargo.toml +++ b/crates/subspace-runtime/Cargo.toml @@ -157,4 +157,5 @@ runtime-benchmarks = [ "sp-consensus-subspace/runtime-benchmarks", "sp-messenger/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "subspace-runtime-primitives/runtime-benchmarks", ] diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index ca0aa3544b1..19eeab6c41a 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -16,7 +16,6 @@ mod domains; mod fees; mod object_mapping; -mod signed_extensions; extern crate alloc; @@ -26,7 +25,6 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); use crate::fees::{OnChargeTransaction, TransactionByteFee}; use crate::object_mapping::extract_block_object_mapping; -pub use crate::signed_extensions::DisablePallets; use alloc::borrow::Cow; use core::mem; use core::num::NonZeroU64; @@ -100,6 +98,8 @@ use subspace_core_primitives::solutions::{ SolutionRange, pieces_to_solution_range, solution_range_to_pieces, }; use subspace_core_primitives::{PublicKey, Randomness, SlotNumber, U256}; +pub use subspace_runtime_primitives::extension::BalanceTransferCheckExtension; +use subspace_runtime_primitives::extension::{BalanceTransferChecks, MaybeBalancesCall}; use subspace_runtime_primitives::utility::{ DefaultNonceProvider, MaybeMultisigCall, MaybeNestedCall, MaybeUtilityCall, }; @@ -415,6 +415,27 @@ impl pallet_utility::Config for Runtime { type WeightInfo = pallet_utility::weights::SubstrateWeight; } +impl MaybeBalancesCall for RuntimeCall { + fn maybe_balance_call(&self) -> Option<&pallet_balances::Call> { + match self { + RuntimeCall::Balances(call) => Some(call), + _ => None, + } + } +} + +impl BalanceTransferChecks for Runtime { + fn is_balance_transferable() -> bool { + let enabled = RuntimeConfigs::enable_balance_transfers(); + // for benchmarks, always return enabled. + if cfg!(feature = "runtime-benchmarks") { + true + } else { + enabled + } + } +} + impl MaybeMultisigCall for RuntimeCall { /// If this call is a `pallet_multisig::Call` call, returns the inner call. fn maybe_multisig_call(&self) -> Option<&pallet_multisig::Call> { @@ -958,6 +979,12 @@ impl pallet_runtime_configs::Config for Runtime { type WeightInfo = pallet_runtime_configs::weights::SubstrateWeight; } +impl pallet_domains::extensions::DomainsCheck for Runtime { + fn is_domains_enabled() -> bool { + RuntimeConfigs::enable_domains() + } +} + mod mmr { use super::Runtime; pub use pallet_mmr::primitives::*; @@ -1085,7 +1112,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, - DisablePallets, + BalanceTransferCheckExtension, pallet_subspace::extensions::SubspaceExtension, pallet_domains::extensions::DomainsExtension, pallet_messenger::extensions::MessengerExtension, @@ -1187,7 +1214,7 @@ fn create_unsigned_general_extrinsic(call: RuntimeCall) -> UncheckedExtrinsic { // for unsigned extrinsic, transaction fee check will be skipped // so set a default value pallet_transaction_payment::ChargeTransactionPayment::::from(0u128), - DisablePallets, + BalanceTransferCheckExtension::::default(), pallet_subspace::extensions::SubspaceExtension::::new(), pallet_domains::extensions::DomainsExtension::::new(), pallet_messenger::extensions::MessengerExtension::::new(), @@ -1262,6 +1289,7 @@ mod benches { [pallet_transporter, Transporter] [pallet_subspace_extension, SubspaceExtensionBench::] [pallet_messenger_from_domains_extension, MessengerFromDomainsExtensionBench::] + [balance_transfer_check_extension, BalanceTransferCheckBench::] ); } @@ -1801,6 +1829,7 @@ impl_runtime_apis! { use baseline::Pallet as BaselineBench; use pallet_subspace::extensions::benchmarking::Pallet as SubspaceExtensionBench; use pallet_messenger::extensions::benchmarking_from_domains::Pallet as MessengerFromDomainsExtensionBench; + use subspace_runtime_primitives::extension::benchmarking::Pallet as BalanceTransferCheckBench; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1820,6 +1849,7 @@ impl_runtime_apis! { use baseline::Pallet as BaselineBench; use pallet_subspace::extensions::benchmarking::Pallet as SubspaceExtensionBench; use pallet_messenger::extensions::benchmarking_from_domains::Pallet as MessengerFromDomainsExtensionBench; + use subspace_runtime_primitives::extension::benchmarking::Pallet as BalanceTransferCheckBench; use frame_support::traits::WhitelistedStorageKeys; let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); diff --git a/crates/subspace-runtime/src/signed_extensions.rs b/crates/subspace-runtime/src/signed_extensions.rs deleted file mode 100644 index c3630f0b011..00000000000 --- a/crates/subspace-runtime/src/signed_extensions.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::{Runtime, RuntimeCall, RuntimeConfigs}; -use frame_support::pallet_prelude::Weight; -use frame_system::pallet_prelude::{OriginFor, RuntimeCallFor}; -use parity_scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::impl_tx_ext_default; -use sp_runtime::traits::{ - AsSystemOriginSigner, DispatchInfoOf, TransactionExtension, ValidateResult, -}; -use sp_runtime::transaction_validity::{ - InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, - ValidTransaction, -}; -use sp_std::prelude::*; -use subspace_runtime_primitives::utility::nested_call_iter; - -/// Disable balance transfers, if configured in the runtime. -#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] -pub struct DisablePallets; - -impl DisablePallets { - fn do_validate_unsigned(call: &RuntimeCall) -> TransactionValidity { - if matches!(call, RuntimeCall::Domains(_)) && !RuntimeConfigs::enable_domains() { - InvalidTransaction::Call.into() - } else { - Ok(ValidTransaction::default()) - } - } - - fn do_validate_signed(call: &RuntimeCall) -> TransactionValidity { - // Disable normal balance transfers. - if !RuntimeConfigs::enable_balance_transfers() && contains_balance_transfer(call) { - Err(InvalidTransaction::Call.into()) - } else { - Ok(ValidTransaction::default()) - } - } -} - -impl TransactionExtension for DisablePallets { - const IDENTIFIER: &'static str = "DisablePallets"; - type Implicit = (); - type Val = (); - type Pre = (); - - // TODO: calculate weight for extension - fn weight(&self, _call: &RuntimeCall) -> Weight { - // there is always one storage read - ::DbWeight::get().reads(1) - } - - fn validate( - &self, - origin: OriginFor, - call: &RuntimeCallFor, - _info: &DispatchInfoOf>, - _len: usize, - _self_implicit: Self::Implicit, - _inherited_implication: &impl Encode, - _source: TransactionSource, - ) -> ValidateResult> { - let validity = if origin.as_system_origin_signer().is_some() { - Self::do_validate_signed(call)? - } else { - ValidTransaction::default() - }; - - Ok((validity, (), origin)) - } - - impl_tx_ext_default!(RuntimeCallFor; prepare); - - fn bare_validate( - call: &RuntimeCallFor, - _info: &DispatchInfoOf>, - _len: usize, - ) -> TransactionValidity { - Self::do_validate_unsigned(call) - } - - fn bare_validate_and_prepare( - call: &RuntimeCallFor, - _info: &DispatchInfoOf>, - _len: usize, - ) -> Result<(), TransactionValidityError> { - Self::do_validate_unsigned(call)?; - Ok(()) - } -} - -fn contains_balance_transfer(call: &RuntimeCall) -> bool { - for call in nested_call_iter::(call) { - // Any other calls might contain nested calls, so we can only return early if we find a - // balance transfer call. - if let RuntimeCall::Balances( - pallet_balances::Call::transfer_allow_death { .. } - | pallet_balances::Call::transfer_keep_alive { .. } - | pallet_balances::Call::transfer_all { .. }, - ) = call - { - return true; - } - } - - false -} diff --git a/test/subspace-test-client/src/chain_spec.rs b/test/subspace-test-client/src/chain_spec.rs index 6de2b170d10..44f7c9c4038 100644 --- a/test/subspace-test-client/src/chain_spec.rs +++ b/test/subspace-test-client/src/chain_spec.rs @@ -6,10 +6,13 @@ use sp_domains::{EvmType, PermissionedActionAllowedBy}; use sp_runtime::traits::{IdentifyAccount, Verify}; use std::marker::PhantomData; use std::num::NonZeroU32; -use subspace_runtime_primitives::{AI3, AccountId, Balance, Signature}; +use subspace_runtime_primitives::{ + AI3, AccountId, Balance, CouncilDemocracyConfigParams, Signature, +}; use subspace_test_runtime::{ AllowAuthoringBy, BalancesConfig, DomainsConfig, EnableRewardsAt, RewardsConfig, - RuntimeGenesisConfig, SubspaceConfig, SudoConfig, SystemConfig, WASM_BINARY, + RuntimeConfigsConfig, RuntimeGenesisConfig, SubspaceConfig, SudoConfig, SystemConfig, + WASM_BINARY, }; /// Generate a crypto pair from seed. @@ -129,6 +132,12 @@ fn create_genesis_config( .expect("hard-coded values are valid; qed"), ], }, - runtime_configs: Default::default(), + runtime_configs: RuntimeConfigsConfig { + enable_domains: true, + enable_dynamic_cost_of_storage: false, + enable_balance_transfers: false, + confirmation_depth_k: 100u32, + council_democracy_config_params: CouncilDemocracyConfigParams::default(), + }, }) } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 46d20cf950a..2787a91773d 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -55,7 +55,7 @@ use frame_support::weights::constants::{ParityDbWeight, WEIGHT_REF_TIME_PER_SECO use frame_support::weights::{ConstantMultiplier, Weight}; use frame_support::{PalletId, construct_runtime, parameter_types}; use frame_system::limits::{BlockLength, BlockWeights}; -use frame_system::pallet_prelude::{OriginFor, RuntimeCallFor}; +use frame_system::pallet_prelude::RuntimeCallFor; use pallet_balances::NegativeImbalance; pub use pallet_rewards::RewardPoint; pub use pallet_subspace::{AllowAuthoringBy, EnableRewardsAt}; @@ -86,19 +86,14 @@ use sp_messenger::{ChannelNonce, XdmId}; use sp_messenger_host_functions::{StorageKeyRequest, get_storage_key}; use sp_mmr_primitives::EncodableOpaqueLeaf; use sp_runtime::traits::{ - AccountIdConversion, AccountIdLookup, AsSystemOriginSigner, BlakeTwo256, ConstBool, - DispatchInfoOf, Keccak256, NumberFor, PostDispatchInfoOf, TransactionExtension, ValidateResult, - Zero, + AccountIdConversion, AccountIdLookup, BlakeTwo256, ConstBool, DispatchInfoOf, Keccak256, + NumberFor, PostDispatchInfoOf, Zero, }; use sp_runtime::transaction_validity::{ InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, - ValidTransaction, }; use sp_runtime::type_with_default::TypeWithDefault; -use sp_runtime::{ - AccountId32, ApplyExtrinsicResult, ExtrinsicInclusionMode, Perbill, generic, - impl_tx_ext_default, -}; +use sp_runtime::{AccountId32, ApplyExtrinsicResult, ExtrinsicInclusionMode, Perbill, generic}; use sp_std::collections::btree_map::BTreeMap; use sp_std::collections::btree_set::BTreeSet; use sp_std::marker::PhantomData; @@ -113,8 +108,10 @@ use subspace_core_primitives::segments::{ }; use subspace_core_primitives::solutions::SolutionRange; use subspace_core_primitives::{PublicKey, Randomness, SlotNumber, U256, hashes}; +pub use subspace_runtime_primitives::extension::BalanceTransferCheckExtension; +use subspace_runtime_primitives::extension::{BalanceTransferChecks, MaybeBalancesCall}; use subspace_runtime_primitives::utility::{ - DefaultNonceProvider, MaybeMultisigCall, MaybeNestedCall, MaybeUtilityCall, nested_call_iter, + DefaultNonceProvider, MaybeMultisigCall, MaybeNestedCall, MaybeUtilityCall, }; use subspace_runtime_primitives::{ AI3, AccountId, Balance, BlockHashFor, BlockNumber, ConsensusEventSegmentSize, ExtrinsicFor, @@ -554,6 +551,21 @@ impl pallet_utility::Config for Runtime { type WeightInfo = pallet_utility::weights::SubstrateWeight; } +impl MaybeBalancesCall for RuntimeCall { + fn maybe_balance_call(&self) -> Option<&pallet_balances::Call> { + match self { + RuntimeCall::Balances(call) => Some(call), + _ => None, + } + } +} + +impl BalanceTransferChecks for Runtime { + fn is_balance_transferable() -> bool { + RuntimeConfigs::enable_balance_transfers() + } +} + impl MaybeMultisigCall for RuntimeCall { /// If this call is a `pallet_multisig::Call` call, returns the inner call. fn maybe_multisig_call(&self) -> Option<&pallet_multisig::Call> { @@ -941,6 +953,12 @@ impl pallet_runtime_configs::Config for Runtime { type WeightInfo = pallet_runtime_configs::weights::SubstrateWeight; } +impl pallet_domains::extensions::DomainsCheck for Runtime { + fn is_domains_enabled() -> bool { + RuntimeConfigs::enable_domains() + } +} + parameter_types! { pub const MaxSignatories: u32 = 100; } @@ -1024,7 +1042,7 @@ pub type SignedExtra = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, - DisablePallets, + BalanceTransferCheckExtension, pallet_subspace::extensions::SubspaceExtension, pallet_domains::extensions::DomainsExtension, pallet_messenger::extensions::MessengerExtension, @@ -1262,7 +1280,7 @@ fn create_unsigned_general_extrinsic(call: RuntimeCall) -> UncheckedExtrinsic { // for unsigned extrinsic, transaction fee check will be skipped // so set a default value pallet_transaction_payment::ChargeTransactionPayment::::from(0u128), - DisablePallets, + BalanceTransferCheckExtension::::default(), pallet_subspace::extensions::SubspaceExtension::::new(), pallet_domains::extensions::DomainsExtension::::new(), pallet_messenger::extensions::MessengerExtension::::new(), @@ -1857,97 +1875,6 @@ impl_runtime_apis! { } } -/// Disable balance transfers, if configured in the runtime. -#[derive(Debug, Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] -pub struct DisablePallets; - -impl DisablePallets { - fn do_validate_unsigned(call: &RuntimeCall) -> TransactionValidity { - if matches!(call, RuntimeCall::Domains(_)) && !RuntimeConfigs::enable_domains() { - InvalidTransaction::Call.into() - } else { - Ok(ValidTransaction::default()) - } - } - - fn do_validate_signed(call: &RuntimeCall) -> TransactionValidity { - // Disable normal balance transfers. - if !RuntimeConfigs::enable_balance_transfers() && contains_balance_transfer(call) { - Err(InvalidTransaction::Call.into()) - } else { - Ok(ValidTransaction::default()) - } - } -} - -impl TransactionExtension for DisablePallets { - const IDENTIFIER: &'static str = "DisablePallets"; - type Implicit = (); - type Val = (); - type Pre = (); - - // TODO: calculate weight for extension - fn weight(&self, _call: &RuntimeCall) -> Weight { - // there is always one storage read - ::DbWeight::get().reads(1) - } - - fn validate( - &self, - origin: OriginFor, - call: &RuntimeCallFor, - _info: &DispatchInfoOf>, - _len: usize, - _self_implicit: Self::Implicit, - _inherited_implication: &impl Encode, - _source: TransactionSource, - ) -> ValidateResult> { - let validity = if origin.as_system_origin_signer().is_some() { - Self::do_validate_signed(call)? - } else { - ValidTransaction::default() - }; - - Ok((validity, (), origin)) - } - - impl_tx_ext_default!(RuntimeCallFor; prepare); - - fn bare_validate( - call: &RuntimeCallFor, - _info: &DispatchInfoOf>, - _len: usize, - ) -> TransactionValidity { - Self::do_validate_unsigned(call) - } - - fn bare_validate_and_prepare( - call: &RuntimeCallFor, - _info: &DispatchInfoOf>, - _len: usize, - ) -> Result<(), TransactionValidityError> { - Self::do_validate_unsigned(call)?; - Ok(()) - } -} - -fn contains_balance_transfer(call: &RuntimeCall) -> bool { - for call in nested_call_iter::(call) { - // Any other calls might contain nested calls, so we can only return early if we find a - // balance transfer call. - if let RuntimeCall::Balances( - pallet_balances::Call::transfer_allow_death { .. } - | pallet_balances::Call::transfer_keep_alive { .. } - | pallet_balances::Call::transfer_all { .. }, - ) = call - { - return true; - } - } - - false -} - #[cfg(test)] mod tests { use crate::Runtime; diff --git a/test/subspace-test-service/src/lib.rs b/test/subspace-test-service/src/lib.rs index f77cf231bd8..f2aa87499f1 100644 --- a/test/subspace-test-service/src/lib.rs +++ b/test/subspace-test-service/src/lib.rs @@ -89,6 +89,7 @@ use std::time::Duration; use subspace_core_primitives::pot::PotOutput; use subspace_core_primitives::solutions::Solution; use subspace_core_primitives::{BlockNumber, PublicKey}; +use subspace_runtime_primitives::extension::BalanceTransferCheckExtension; use subspace_runtime_primitives::opaque::Block; use subspace_runtime_primitives::{ AccountId, Balance, BlockHashFor, ExtrinsicFor, Hash, HeaderFor, Signature, @@ -97,8 +98,7 @@ use subspace_service::{FullSelectChain, RuntimeExecutor}; use subspace_test_client::{Backend, Client, chain_spec}; use subspace_test_primitives::OnchainStateApi; use subspace_test_runtime::{ - DisablePallets, Runtime, RuntimeApi, RuntimeCall, SLOT_DURATION, SignedExtra, - UncheckedExtrinsic, + Runtime, RuntimeApi, RuntimeCall, SLOT_DURATION, SignedExtra, UncheckedExtrinsic, }; use substrate_frame_rpc_system::AccountNonceApi; use substrate_test_client::{RpcHandlersExt, RpcTransactionError, RpcTransactionOutput}; @@ -1412,7 +1412,7 @@ fn get_signed_extra( frame_system::CheckNonce::::from(nonce.into()), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - DisablePallets, + BalanceTransferCheckExtension::::default(), pallet_subspace::extensions::SubspaceExtension::::new(), pallet_domains::extensions::DomainsExtension::::new(), pallet_messenger::extensions::MessengerExtension::::new(),