diff --git a/Cargo.lock b/Cargo.lock index 895b55e447..96df6846fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7619,6 +7619,7 @@ dependencies = [ "sp-keyring", "sp-keystore", "sp-runtime", + "sp-std 14.0.0 (git+https://github.com/paritytech/polkadot-sdk.git?tag=polkadot-stable2503-6)", "subtensor-macros", "tle", "w3f-bls", diff --git a/docs/transaction-priority.md b/docs/transaction-priority.md new file mode 100644 index 0000000000..36ebf79e64 --- /dev/null +++ b/docs/transaction-priority.md @@ -0,0 +1,36 @@ +## Transaction Priority + +### Overview +In Subtensor, transaction priority is determined by custom transaction extensions, which alter or override the default Substrate SDK behavior. Extensions affecting transaction priority are: + +- **`ChargeTransactionPaymentWrapper`** (wraps `ChargeTransactionPayment`) +- **`DrandPriority`** + +Substrate SDK combines priorities from all transaction extensions using addition. + +--- + +### 1. `ChargeTransactionPaymentWrapper` +In the Substrate SDK, `ChargeTransactionPayment` normally calculates transaction priority based on: +- **Tip** — an extra fee paid by the sender. +- **Weight** — computational complexity of the transaction. +- **Dispatch class** — category of the transaction (`Normal`, `Operational`, `Mandatory`). + +However, in Subtensor, `ChargeTransactionPaymentWrapper` **overrides** this logic. +It replaces the dynamic calculation with a **flat priority scale** based only on the dispatch class. + +#### Current priority values: +| Dispatch Class | Priority Value | Notes | +|---------------------|-------------------|--------------------------------------------------------------| +| `Normal` | `1` | Standard transactions | +| `Mandatory` | `1` | Rarely used, same as `Normal` | +| `Operational` | `10_000_000_000` | Reserved for critical system extrinsics (e.g.: `sudo` calls) | + + +--- + +### 2. `DrandPriority` + +Special pallet_drand priority: 10_000 for `write_pulse` extrinsic. + +--- \ No newline at end of file diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index df21d5bef4..ad2abfc935 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -5,8 +5,8 @@ use crate::client::FullClient; use node_subtensor_runtime as runtime; -use node_subtensor_runtime::check_nonce; use node_subtensor_runtime::pallet_subtensor; +use node_subtensor_runtime::{check_nonce, transaction_payment_wrapper}; use runtime::{BalancesCall, SystemCall}; use sc_cli::Result; use sc_client_api::BlockBackend; @@ -123,21 +123,27 @@ pub fn create_benchmark_extrinsic( .checked_next_power_of_two() .map(|c| c / 2) .unwrap_or(2) as u64; - let extra: runtime::TransactionExtensions = ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( - period, - best_block.saturated_into(), - )), - check_nonce::CheckNonce::::from(nonce), - frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(0), - pallet_subtensor::SubtensorTransactionExtension::::new(), - frame_metadata_hash_extension::CheckMetadataHash::::new(true), - ); + let extra: runtime::TransactionExtensions = + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( + period, + best_block.saturated_into(), + )), + check_nonce::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + transaction_payment_wrapper::ChargeTransactionPaymentWrapper::new( + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ), + pallet_subtensor::transaction_extension::SubtensorTransactionExtension::< + runtime::Runtime, + >::new(), + pallet_drand::drand_priority::DrandPriority::::new(), + frame_metadata_hash_extension::CheckMetadataHash::::new(true), + ); let raw_payload = runtime::SignedPayload::from_raw( call.clone(), @@ -152,6 +158,7 @@ pub fn create_benchmark_extrinsic( (), (), (), + (), None, ), ); diff --git a/pallets/commitments/src/lib.rs b/pallets/commitments/src/lib.rs index 7e3b1704a0..34192b6fa2 100644 --- a/pallets/commitments/src/lib.rs +++ b/pallets/commitments/src/lib.rs @@ -208,7 +208,7 @@ pub mod pallet { Weight::from_parts(33_480_000, 0) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::No ))] pub fn set_commitment( diff --git a/pallets/drand/Cargo.toml b/pallets/drand/Cargo.toml index c6a0705627..269e993d02 100644 --- a/pallets/drand/Cargo.toml +++ b/pallets/drand/Cargo.toml @@ -27,6 +27,7 @@ frame-system.workspace = true sp-core.workspace = true sp-io.workspace = true sp-runtime.workspace = true +sp-std.workspace = true # arkworks dependencies sp-ark-bls12-381.workspace = true ark-bls12-381 = { workspace = true, features = ["curve"] } @@ -55,6 +56,7 @@ std = [ "frame-system/std", "scale-info/std", "sp-core/std", + "sp-std/std", "sp-io/std", "sp-keystore/std", "sp-keyring/std", diff --git a/pallets/drand/src/drand_priority.rs b/pallets/drand/src/drand_priority.rs new file mode 100644 index 0000000000..c63ffa5803 --- /dev/null +++ b/pallets/drand/src/drand_priority.rs @@ -0,0 +1,89 @@ +use crate::{Call, Config}; +use codec::{Decode, DecodeWithMemTracking, Encode}; +use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; +use frame_support::pallet_prelude::Weight; +use frame_support::traits::IsSubType; +use scale_info::TypeInfo; +use sp_runtime::traits::{ + DispatchInfoOf, DispatchOriginOf, Dispatchable, Implication, TransactionExtension, + ValidateResult, +}; +use sp_runtime::transaction_validity::{ + TransactionPriority, TransactionSource, TransactionValidityError, ValidTransaction, +}; +use sp_std::marker::PhantomData; +use subtensor_macros::freeze_struct; + +pub type RuntimeCallFor = ::RuntimeCall; + +#[freeze_struct("d0d094192bd6390e")] +#[derive(Default, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] +pub struct DrandPriority(pub PhantomData); + +impl sp_std::fmt::Debug for DrandPriority { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "DrandPriority") + } +} + +impl DrandPriority { + pub fn new() -> Self { + Self(PhantomData) + } + + fn get_drand_priority() -> TransactionPriority { + 10_000u64 + } +} + +impl TransactionExtension> + for DrandPriority +where + ::RuntimeCall: + Dispatchable, + ::RuntimeCall: IsSubType>, +{ + const IDENTIFIER: &'static str = "DrandPriority"; + type Implicit = (); + type Val = (); + type Pre = (); + + fn weight(&self, _call: &RuntimeCallFor) -> Weight { + // TODO: benchmark transaction extension + Weight::zero() + } + + fn validate( + &self, + origin: DispatchOriginOf>, + call: &RuntimeCallFor, + _info: &DispatchInfoOf>, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Implication, + _source: TransactionSource, + ) -> ValidateResult> { + match call.is_sub_type() { + Some(Call::write_pulse { .. }) => { + let validity = ValidTransaction { + priority: Self::get_drand_priority(), + ..Default::default() + }; + + Ok((validity, (), origin)) + } + _ => Ok((Default::default(), (), origin)), + } + } + + fn prepare( + self, + _val: Self::Val, + _origin: &DispatchOriginOf>, + _call: &RuntimeCallFor, + _info: &DispatchInfoOf>, + _len: usize, + ) -> Result { + Ok(()) + } +} diff --git a/pallets/drand/src/lib.rs b/pallets/drand/src/lib.rs index 0acf8ec493..145aeb6657 100644 --- a/pallets/drand/src/lib.rs +++ b/pallets/drand/src/lib.rs @@ -58,6 +58,7 @@ use sp_runtime::{ }; pub mod bls12_381; +pub mod drand_priority; pub mod migrations; pub mod types; pub mod utils; @@ -404,9 +405,9 @@ pub mod pallet { /// * `origin`: the root user /// * `config`: the beacon configuration #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(8_766_000, 0) + #[pallet::weight((Weight::from_parts(8_766_000, 0) .saturating_add(T::DbWeight::get().reads(0_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)))] + .saturating_add(T::DbWeight::get().writes(2_u64)), DispatchClass::Operational))] pub fn set_beacon_config( origin: OriginFor, config_payload: BeaconConfigurationPayload>, @@ -425,9 +426,9 @@ pub mod pallet { /// allows the root user to set the oldest stored round #[pallet::call_index(2)] - #[pallet::weight(Weight::from_parts(5_370_000, 0) + #[pallet::weight((Weight::from_parts(5_370_000, 0) .saturating_add(T::DbWeight::get().reads(0_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)))] + .saturating_add(T::DbWeight::get().writes(1_u64)), DispatchClass::Operational))] pub fn set_oldest_stored_round(origin: OriginFor, oldest_round: u64) -> DispatchResult { ensure_root(origin)?; OldestStoredRound::::put(oldest_round); diff --git a/pallets/registry/src/lib.rs b/pallets/registry/src/lib.rs index e4e99e4459..a4f7571bea 100644 --- a/pallets/registry/src/lib.rs +++ b/pallets/registry/src/lib.rs @@ -113,7 +113,7 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set_identity(), - DispatchClass::Operational + DispatchClass::Normal ))] pub fn set_identity( origin: OriginFor, diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ea4beb3f2b..0be26a81be 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -8,28 +8,20 @@ use frame_system::{self as system, ensure_signed}; pub use pallet::*; -use codec::{Decode, DecodeWithMemTracking, Encode}; +use codec::{Decode, Encode}; use frame_support::sp_runtime::transaction_validity::InvalidTransaction; -use frame_support::sp_runtime::transaction_validity::ValidTransaction; use frame_support::{ - dispatch::{self, DispatchInfo, DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo}, + dispatch::{self, DispatchResult, DispatchResultWithPostInfo}, ensure, pallet_macros::import_section, pallet_prelude::*, - traits::{IsSubType, tokens::fungible}, + traits::tokens::fungible, }; use pallet_balances::Call as BalancesCall; // use pallet_scheduler as Scheduler; use scale_info::TypeInfo; use sp_core::Get; -use sp_runtime::{ - DispatchError, - traits::{ - AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, - TransactionExtension, ValidateResult, - }, - transaction_validity::{TransactionValidity, TransactionValidityError}, -}; +use sp_runtime::{DispatchError, transaction_validity::TransactionValidityError}; use sp_std::marker::PhantomData; use subtensor_runtime_common::{AlphaCurrency, Currency, NetUid, TaoCurrency}; @@ -55,6 +47,7 @@ use macros::{config, dispatches, errors, events, genesis, hooks}; #[cfg(test)] mod tests; +pub mod transaction_extension; // apparently this is stabilized since rust 1.36 extern crate alloc; @@ -1803,40 +1796,6 @@ pub mod pallet { // ---- Subtensor helper functions. impl Pallet { - /// Returns the transaction priority for setting weights. - pub fn get_priority_set_weights(hotkey: &T::AccountId, netuid: NetUid) -> u64 { - if let Ok(uid) = Self::get_uid_for_net_and_hotkey(netuid, hotkey) { - // TODO rethink this. - let _stake = Self::get_inherited_for_hotkey_on_subnet(hotkey, netuid); - let current_block_number: u64 = Self::get_current_block_as_u64(); - let default_priority: u64 = - current_block_number.saturating_sub(Self::get_last_update_for_uid(netuid, uid)); - return default_priority.saturating_add(u32::MAX as u64); - } - 0 - } - - // FIXME this function is used both to calculate for alpha stake amount as well as tao - // amount - /// Returns the transaction priority for stake operations. - pub fn get_priority_staking( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - stake_amount: u64, - ) -> u64 { - match LastColdkeyHotkeyStakeBlock::::get(coldkey, hotkey) { - Some(last_stake_block) => { - let current_block_number = Self::get_current_block_as_u64(); - let default_priority = current_block_number.saturating_sub(last_stake_block); - - default_priority - .saturating_add(u32::MAX as u64) - .saturating_add(stake_amount) - } - None => stake_amount, - } - } - /// Is the caller allowed to set weights pub fn check_weights_min_stake(hotkey: &T::AccountId, netuid: NetUid) -> bool { // Blacklist weights transactions for low stake peers. @@ -1879,22 +1838,6 @@ pub mod pallet { } } -/************************************************************ - CallType definition -************************************************************/ -#[derive(Debug, PartialEq, Default)] -pub enum CallType { - SetWeights, - AddStake, - RemoveStake, - AddDelegate, - Register, - Serve, - RegisterNetwork, - #[default] - Other, -} - #[derive(Debug, PartialEq)] pub enum CustomTransactionError { ColdkeyInSwapSchedule, @@ -1952,533 +1895,6 @@ impl From for TransactionValidityError { } } -#[freeze_struct("2e02eb32e5cb25d3")] -#[derive(Default, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] -pub struct SubtensorTransactionExtension(pub PhantomData); - -impl sp_std::fmt::Debug for SubtensorTransactionExtension { - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - write!(f, "SubtensorTransactionExtension") - } -} - -impl SubtensorTransactionExtension -where - ::RuntimeCall: - Dispatchable, - ::RuntimeCall: IsSubType>, -{ - pub fn new() -> Self { - Self(Default::default()) - } - - pub fn get_priority_vanilla() -> u64 { - // Return high priority so that every extrinsic except set_weights function will - // have a higher priority than the set_weights call - u64::MAX - } - - pub fn get_priority_set_weights(who: &T::AccountId, netuid: NetUid) -> u64 { - Pallet::::get_priority_set_weights(who, netuid) - } - - pub fn get_priority_staking( - coldkey: &T::AccountId, - hotkey: &T::AccountId, - stake_amount: u64, - ) -> u64 { - Pallet::::get_priority_staking(coldkey, hotkey, stake_amount) - } - - pub fn check_weights_min_stake(who: &T::AccountId, netuid: NetUid) -> bool { - Pallet::::check_weights_min_stake(who, netuid) - } - - pub fn validity_ok(priority: u64) -> ValidTransaction { - ValidTransaction { - priority, - ..Default::default() - } - } - - pub fn result_to_validity(result: Result<(), Error>, priority: u64) -> TransactionValidity { - if let Err(err) = result { - Err(match err { - Error::::AmountTooLow => CustomTransactionError::StakeAmountTooLow.into(), - Error::::SubnetNotExists => CustomTransactionError::SubnetDoesntExist.into(), - Error::::NotEnoughBalanceToStake => CustomTransactionError::BalanceTooLow.into(), - Error::::HotKeyAccountNotExists => { - CustomTransactionError::HotkeyAccountDoesntExist.into() - } - Error::::NotEnoughStakeToWithdraw => { - CustomTransactionError::NotEnoughStakeToWithdraw.into() - } - Error::::InsufficientLiquidity => { - CustomTransactionError::InsufficientLiquidity.into() - } - Error::::SlippageTooHigh => CustomTransactionError::SlippageTooHigh.into(), - Error::::TransferDisallowed => CustomTransactionError::TransferDisallowed.into(), - Error::::HotKeyNotRegisteredInNetwork => { - CustomTransactionError::HotKeyNotRegisteredInNetwork.into() - } - Error::::InvalidIpAddress => CustomTransactionError::InvalidIpAddress.into(), - Error::::ServingRateLimitExceeded => { - CustomTransactionError::ServingRateLimitExceeded.into() - } - Error::::InvalidPort => CustomTransactionError::InvalidPort.into(), - _ => CustomTransactionError::BadRequest.into(), - }) - } else { - Ok(ValidTransaction { - priority, - ..Default::default() - }) - } - } -} - -impl - TransactionExtension<::RuntimeCall> - for SubtensorTransactionExtension -where - ::RuntimeCall: - Dispatchable, - ::RuntimeOrigin: AsSystemOriginSigner + Clone, - ::RuntimeCall: IsSubType>, - ::RuntimeCall: IsSubType>, -{ - const IDENTIFIER: &'static str = "SubtensorTransactionExtension"; - - type Implicit = (); - type Val = Option; - type Pre = Option; - - fn weight(&self, _call: &::RuntimeCall) -> Weight { - // TODO: benchmark transaction extension - Weight::zero() - } - - fn validate( - &self, - origin: ::RuntimeOrigin, - call: &::RuntimeCall, - _info: &DispatchInfoOf<::RuntimeCall>, - _len: usize, - _self_implicit: Self::Implicit, - _inherited_implication: &impl Implication, - _source: TransactionSource, - ) -> ValidateResult::RuntimeCall> { - // Ensure the transaction is signed, else we just skip the extension. - let Some(who) = origin.as_system_origin_signer() else { - return Ok((Default::default(), None, origin)); - }; - - match call.is_sub_type() { - Some(Call::commit_weights { netuid, .. }) => { - if Self::check_weights_min_stake(who, *netuid) { - let priority: u64 = Self::get_priority_set_weights(who, *netuid); - let validity = Self::validity_ok(priority); - Ok((validity, Some(who.clone()), origin)) - } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) - } - } - Some(Call::reveal_weights { - netuid, - uids, - values, - salt, - version_key, - }) => { - if Self::check_weights_min_stake(who, *netuid) { - let provided_hash = Pallet::::get_commit_hash( - who, - *netuid, - uids, - values, - salt, - *version_key, - ); - match Pallet::::find_commit_block_via_hash(provided_hash) { - Some(commit_block) => { - if Pallet::::is_reveal_block_range(*netuid, commit_block) { - let priority: u64 = Self::get_priority_set_weights(who, *netuid); - let validity = Self::validity_ok(priority); - Ok((validity, Some(who.clone()), origin)) - } else { - Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) - } - } - None => Err(CustomTransactionError::CommitNotFound.into()), - } - } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) - } - } - Some(Call::batch_reveal_weights { - netuid, - uids_list, - values_list, - salts_list, - version_keys, - }) => { - if Self::check_weights_min_stake(who, *netuid) { - let num_reveals = uids_list.len(); - if num_reveals == values_list.len() - && num_reveals == salts_list.len() - && num_reveals == version_keys.len() - { - let provided_hashs = (0..num_reveals) - .map(|i| { - Pallet::::get_commit_hash( - who, - *netuid, - uids_list.get(i).unwrap_or(&Vec::new()), - values_list.get(i).unwrap_or(&Vec::new()), - salts_list.get(i).unwrap_or(&Vec::new()), - *version_keys.get(i).unwrap_or(&0_u64), - ) - }) - .collect::>(); - - let batch_reveal_block = provided_hashs - .iter() - .filter_map(|hash| Pallet::::find_commit_block_via_hash(*hash)) - .collect::>(); - - if provided_hashs.len() == batch_reveal_block.len() { - if Pallet::::is_batch_reveal_block_range(*netuid, batch_reveal_block) - { - let priority: u64 = Self::get_priority_set_weights(who, *netuid); - let validity = Self::validity_ok(priority); - Ok((validity, Some(who.clone()), origin)) - } else { - Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) - } - } else { - Err(CustomTransactionError::CommitNotFound.into()) - } - } else { - Err(CustomTransactionError::InputLengthsUnequal.into()) - } - } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) - } - } - Some(Call::set_weights { netuid, .. }) => { - if Self::check_weights_min_stake(who, *netuid) { - let priority: u64 = Self::get_priority_set_weights(who, *netuid); - let validity = Self::validity_ok(priority); - Ok((validity, Some(who.clone()), origin)) - } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) - } - } - Some(Call::set_tao_weights { netuid, hotkey, .. }) => { - if Self::check_weights_min_stake(hotkey, *netuid) { - let priority: u64 = Self::get_priority_set_weights(hotkey, *netuid); - let validity = Self::validity_ok(priority); - Ok((validity, Some(who.clone()), origin)) - } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) - } - } - Some(Call::commit_crv3_weights { - netuid, - reveal_round, - .. - }) => { - if Self::check_weights_min_stake(who, *netuid) { - if *reveal_round < pallet_drand::LastStoredRound::::get() { - return Err(CustomTransactionError::InvalidRevealRound.into()); - } - let priority: u64 = Pallet::::get_priority_set_weights(who, *netuid); - let validity = Self::validity_ok(priority); - Ok((validity, Some(who.clone()), origin)) - } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) - } - } - Some(Call::commit_timelocked_weights { - netuid, - reveal_round, - .. - }) => { - if Self::check_weights_min_stake(who, *netuid) { - if *reveal_round < pallet_drand::LastStoredRound::::get() { - return Err(CustomTransactionError::InvalidRevealRound.into()); - } - let priority: u64 = Pallet::::get_priority_set_weights(who, *netuid); - let validity = Self::validity_ok(priority); - Ok((validity, Some(who.clone()), origin)) - } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) - } - } - Some(Call::add_stake { - hotkey, - netuid: _, - amount_staked, - }) => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - let validity = Self::validity_ok(Self::get_priority_staking( - who, - hotkey, - (*amount_staked).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::add_stake_limit { - hotkey, - netuid: _, - amount_staked, - .. - }) => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - - let validity = Self::validity_ok(Self::get_priority_staking( - who, - hotkey, - (*amount_staked).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::remove_stake { - hotkey, - netuid: _, - amount_unstaked, - }) => { - let validity = Self::validity_ok(Self::get_priority_staking( - who, - hotkey, - (*amount_unstaked).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::remove_stake_limit { - hotkey, - netuid: _, - amount_unstaked, - .. - }) => { - let validity = Self::validity_ok(Self::get_priority_staking( - who, - hotkey, - (*amount_unstaked).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::move_stake { - origin_hotkey, - destination_hotkey: _, - origin_netuid: _, - destination_netuid: _, - alpha_amount, - }) => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - let validity = Self::validity_ok(Self::get_priority_staking( - who, - origin_hotkey, - (*alpha_amount).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::transfer_stake { - destination_coldkey: _, - hotkey, - origin_netuid: _, - destination_netuid: _, - alpha_amount, - }) => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - let validity = Self::validity_ok(Self::get_priority_staking( - who, - hotkey, - (*alpha_amount).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::swap_stake { - hotkey, - origin_netuid: _, - destination_netuid: _, - alpha_amount, - }) => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - let validity = Self::validity_ok(Self::get_priority_staking( - who, - hotkey, - (*alpha_amount).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::swap_stake_limit { - hotkey, - origin_netuid: _, - destination_netuid: _, - alpha_amount, - .. - }) => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - - let validity = Self::validity_ok(Self::get_priority_staking( - who, - hotkey, - (*alpha_amount).into(), - )); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::register { netuid, .. } | Call::burned_register { netuid, .. }) => { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - - let registrations_this_interval = - Pallet::::get_registrations_this_interval(*netuid); - let max_registrations_per_interval = - Pallet::::get_target_registrations_per_interval(*netuid); - if registrations_this_interval >= (max_registrations_per_interval.saturating_mul(3)) - { - // If the registration limit for the interval is exceeded, reject the transaction - return Err(CustomTransactionError::RateLimitExceeded.into()); - } - let validity = ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }; - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::register_network { .. }) => { - let validity = Self::validity_ok(Self::get_priority_vanilla()); - Ok((validity, Some(who.clone()), origin)) - } - Some(Call::dissolve_network { .. }) => { - if ColdkeySwapScheduled::::contains_key(who) { - Err(CustomTransactionError::ColdkeyInSwapSchedule.into()) - } else { - let validity = Self::validity_ok(Self::get_priority_vanilla()); - Ok((validity, Some(who.clone()), origin)) - } - } - Some(Call::serve_axon { - netuid, - version, - ip, - port, - ip_type, - protocol, - placeholder1, - placeholder2, - }) => { - // Fully validate the user input - Self::result_to_validity( - Pallet::::validate_serve_axon( - who, - *netuid, - *version, - *ip, - *port, - *ip_type, - *protocol, - *placeholder1, - *placeholder2, - ), - Self::get_priority_vanilla(), - ) - .map(|validity| (validity, Some(who.clone()), origin.clone())) - } - _ => { - if let Some( - BalancesCall::transfer_keep_alive { .. } - | BalancesCall::transfer_all { .. } - | BalancesCall::transfer_allow_death { .. }, - ) = call.is_sub_type() - { - if ColdkeySwapScheduled::::contains_key(who) { - return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); - } - } - let validity = Self::validity_ok(Self::get_priority_vanilla()); - Ok((validity, Some(who.clone()), origin)) - } - } - } - - // NOTE: Add later when we put in a pre and post dispatch step. - fn prepare( - self, - val: Self::Val, - _origin: &::RuntimeOrigin, - call: &::RuntimeCall, - _info: &DispatchInfoOf<::RuntimeCall>, - _len: usize, - ) -> Result { - // The transaction is not signed, given val is None, so we just skip this step. - if val.is_none() { - return Ok(None); - } - - match call.is_sub_type() { - Some(Call::add_stake { .. }) => Ok(Some(CallType::AddStake)), - Some(Call::remove_stake { .. }) => Ok(Some(CallType::RemoveStake)), - Some(Call::set_weights { .. }) => Ok(Some(CallType::SetWeights)), - Some(Call::commit_weights { .. }) => Ok(Some(CallType::SetWeights)), - Some(Call::reveal_weights { .. }) => Ok(Some(CallType::SetWeights)), - Some(Call::register { .. }) => Ok(Some(CallType::Register)), - Some(Call::serve_axon { .. }) => Ok(Some(CallType::Serve)), - Some(Call::serve_axon_tls { .. }) => Ok(Some(CallType::Serve)), - Some(Call::register_network { .. }) => Ok(Some(CallType::RegisterNetwork)), - _ => Ok(Some(CallType::Other)), - } - } - - fn post_dispatch( - pre: Self::Pre, - _info: &DispatchInfoOf<::RuntimeCall>, - _post_info: &mut PostDispatchInfoOf<::RuntimeCall>, - _len: usize, - _result: &dispatch::DispatchResult, - ) -> Result<(), TransactionValidityError> { - // Skip this step if the transaction is not signed, meaning pre is None. - let call_type = match pre { - Some(call_type) => call_type, - None => return Ok(()), - }; - - match call_type { - CallType::SetWeights => { - log::debug!("Not Implemented!"); - } - CallType::AddStake => { - log::debug!("Not Implemented! Need to add potential transaction fees here."); - } - CallType::RemoveStake => { - log::debug!("Not Implemented! Need to add potential transaction fees here."); - } - CallType::Register => { - log::debug!("Not Implemented!"); - } - _ => { - log::debug!("Not Implemented!"); - } - } - - Ok(()) - } -} - use sp_std::vec; // TODO: unravel this rats nest, for some reason rustc thinks this is unused even though it's diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index aff5e8cd7a..aea2eac3f7 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -279,7 +279,7 @@ mod dispatches { /// - Attempting to commit when the user has more than the allowed limit of unrevealed commits. /// #[pallet::call_index(99)] - #[pallet::weight((Weight::from_parts(62_300_000, 0) + #[pallet::weight((Weight::from_parts(77_750_000, 0) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] pub fn commit_crv3_weights( @@ -693,7 +693,7 @@ mod dispatches { /// - Attempting to set prometheus information withing the rate limit min. /// #[pallet::call_index(4)] - #[pallet::weight((Weight::from_parts(36_090_000, 0) + #[pallet::weight((Weight::from_parts(43_680_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn serve_axon( @@ -939,7 +939,7 @@ mod dispatches { #[pallet::call_index(70)] #[pallet::weight((Weight::from_parts(275_300_000, 0) .saturating_add(T::DbWeight::get().reads(47)) - .saturating_add(T::DbWeight::get().writes(37)), DispatchClass::Operational, Pays::No))] + .saturating_add(T::DbWeight::get().writes(37)), DispatchClass::Normal, Pays::No))] pub fn swap_hotkey( origin: OriginFor, hotkey: T::AccountId, @@ -1195,7 +1195,7 @@ mod dispatches { #[pallet::call_index(59)] #[pallet::weight((Weight::from_parts(235_400_000, 0) .saturating_add(T::DbWeight::get().reads(36)) - .saturating_add(T::DbWeight::get().writes(52)), DispatchClass::Operational, Pays::No))] + .saturating_add(T::DbWeight::get().writes(52)), DispatchClass::Normal, Pays::No))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) } @@ -1283,7 +1283,7 @@ mod dispatches { #[pallet::call_index(67)] #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Normal, Pays::Yes))] pub fn set_children( origin: T::RuntimeOrigin, hotkey: T::AccountId, @@ -1329,7 +1329,7 @@ mod dispatches { #[pallet::call_index(73)] #[pallet::weight((Weight::from_parts(37_830_000, 0) .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Operational, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::Yes))] pub fn schedule_swap_coldkey( origin: OriginFor, new_coldkey: T::AccountId, @@ -1404,7 +1404,7 @@ mod dispatches { #[pallet::call_index(74)] #[pallet::weight((Weight::from_parts(119_000_000, 0) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Operational, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(31)), DispatchClass::Normal, Pays::Yes))] pub fn schedule_dissolve_network( _origin: OriginFor, _netuid: NetUid, @@ -1540,7 +1540,7 @@ mod dispatches { #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(234_200_000, 0) .saturating_add(T::DbWeight::get().reads(35)) - .saturating_add(T::DbWeight::get().writes(51)), DispatchClass::Operational, Pays::No))] + .saturating_add(T::DbWeight::get().writes(51)), DispatchClass::Normal, Pays::No))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId, @@ -1577,7 +1577,7 @@ mod dispatches { #[pallet::call_index(83)] #[pallet::weight((Weight::from_parts(28_830_000, 0) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Operational, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(0)), DispatchClass::Normal, Pays::Yes))] pub fn unstake_all(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_unstake_all(origin, hotkey) } @@ -1639,7 +1639,7 @@ mod dispatches { #[pallet::call_index(85)] #[pallet::weight((Weight::from_parts(164_300_000, 0) .saturating_add(T::DbWeight::get().reads(15_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)), DispatchClass::Operational, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(7_u64)), DispatchClass::Normal, Pays::Yes))] pub fn move_stake( origin: T::RuntimeOrigin, origin_hotkey: T::AccountId, @@ -1682,7 +1682,7 @@ mod dispatches { #[pallet::call_index(86)] #[pallet::weight((Weight::from_parts(160_300_000, 0) .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Operational, Pays::Yes))] + .saturating_add(T::DbWeight::get().writes(6_u64)), DispatchClass::Normal, Pays::Yes))] pub fn transfer_stake( origin: T::RuntimeOrigin, destination_coldkey: T::AccountId, @@ -1725,7 +1725,7 @@ mod dispatches { Weight::from_parts(351_300_000, 0) .saturating_add(T::DbWeight::get().reads(37_u64)) .saturating_add(T::DbWeight::get().writes(22_u64)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] pub fn swap_stake( @@ -1898,7 +1898,7 @@ mod dispatches { Weight::from_parts(411_500_000, 0) .saturating_add(T::DbWeight::get().reads(37_u64)) .saturating_add(T::DbWeight::get().writes(22_u64)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] pub fn swap_stake_limit( @@ -1932,7 +1932,7 @@ mod dispatches { #[pallet::call_index(91)] #[pallet::weight(( Weight::from_parts(27_150_000, 0).saturating_add(T::DbWeight::get().reads_writes(3, 3)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] pub fn try_associate_hotkey( @@ -1957,7 +1957,7 @@ mod dispatches { #[pallet::call_index(92)] #[pallet::weight(( Weight::from_parts(29_780_000, 0).saturating_add(T::DbWeight::get().reads_writes(4, 2)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] pub fn start_call(origin: T::RuntimeOrigin, netuid: NetUid) -> DispatchResult { @@ -1995,7 +1995,7 @@ mod dispatches { #[pallet::call_index(93)] #[pallet::weight(( Weight::from_parts(3_000_000, 0).saturating_add(T::DbWeight::get().reads_writes(2, 1)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] pub fn associate_evm_key( @@ -2021,7 +2021,7 @@ mod dispatches { #[pallet::call_index(101)] #[pallet::weight(( Weight::from_parts(92_600_000, 0).saturating_add(T::DbWeight::get().reads_writes(7, 4)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] pub fn recycle_alpha( @@ -2046,7 +2046,7 @@ mod dispatches { #[pallet::call_index(102)] #[pallet::weight(( Weight::from_parts(90_880_000, 0).saturating_add(T::DbWeight::get().reads_writes(7, 3)), - DispatchClass::Operational, + DispatchClass::Normal, Pays::Yes ))] pub fn burn_alpha( @@ -2201,7 +2201,7 @@ mod dispatches { /// * commit_reveal_version (`u16`): /// - The client (bittensor-drand) version #[pallet::call_index(113)] - #[pallet::weight((Weight::from_parts(65_780_000, 0) + #[pallet::weight((Weight::from_parts(81_920_000, 0) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2)), DispatchClass::Normal, Pays::No))] pub fn commit_timelocked_weights( diff --git a/pallets/subtensor/src/tests/registration.rs b/pallets/subtensor/src/tests/registration.rs index 67c3fd3c4d..7ccb591620 100644 --- a/pallets/subtensor/src/tests/registration.rs +++ b/pallets/subtensor/src/tests/registration.rs @@ -12,7 +12,8 @@ use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyT, NetUid}; use super::mock; use super::mock::*; -use crate::{AxonInfoOf, CustomTransactionError, Error, SubtensorTransactionExtension}; +use crate::transaction_extension::SubtensorTransactionExtension; +use crate::{AxonInfoOf, CustomTransactionError, Error}; /******************************************** subscribing::subscribe() tests diff --git a/pallets/subtensor/src/tests/serving.rs b/pallets/subtensor/src/tests/serving.rs index c874831fcb..b4173a8ebb 100644 --- a/pallets/subtensor/src/tests/serving.rs +++ b/pallets/subtensor/src/tests/serving.rs @@ -2,6 +2,7 @@ use super::mock::*; use crate::Error; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; use frame_support::assert_noop; use frame_support::pallet_prelude::Weight; @@ -11,7 +12,7 @@ use frame_support::{ }; use frame_system::{Config, RawOrigin}; use sp_core::U256; -use sp_runtime::traits::TxBaseImplication; +use sp_runtime::traits::{DispatchInfoOf, TransactionExtension, TxBaseImplication}; mod test { use std::net::{Ipv4Addr, Ipv6Addr}; @@ -1402,10 +1403,10 @@ fn test_serve_axon_validate() { placeholder2, }); - let info: crate::DispatchInfo = - crate::DispatchInfoOf::<::RuntimeCall>::default(); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let extension = SubtensorTransactionExtension::::new(); // Submit to the signed extension validate function let result_bad = extension.validate( RawOrigin::Signed(hotkey).into(), diff --git a/pallets/subtensor/src/tests/staking.rs b/pallets/subtensor/src/tests/staking.rs index 9fa9ffa2c0..8345d24fff 100644 --- a/pallets/subtensor/src/tests/staking.rs +++ b/pallets/subtensor/src/tests/staking.rs @@ -10,6 +10,7 @@ use pallet_subtensor_swap::Call as SwapCall; use pallet_subtensor_swap::tick::TickIndex; use safe_math::FixedExt; use sp_core::{Get, H256, U256}; +use sp_runtime::traits::Dispatchable; use substrate_fixed::traits::FromFixed; use substrate_fixed::types::{I96F32, I110F18, U64F64, U96F32}; use subtensor_runtime_common::{AlphaCurrency, Currency as CurrencyT, NetUid, TaoCurrency}; diff --git a/pallets/subtensor/src/tests/swap_coldkey.rs b/pallets/subtensor/src/tests/swap_coldkey.rs index 1f1b9b113b..54bdc253ce 100644 --- a/pallets/subtensor/src/tests/swap_coldkey.rs +++ b/pallets/subtensor/src/tests/swap_coldkey.rs @@ -2,6 +2,7 @@ use approx::assert_abs_diff_eq; use codec::Encode; +use frame_support::dispatch::DispatchInfo; use frame_support::error::BadOrigin; use frame_support::traits::OnInitialize; use frame_support::traits::schedule::DispatchTime; @@ -10,6 +11,7 @@ use frame_support::weights::Weight; use frame_support::{assert_err, assert_noop, assert_ok}; use frame_system::{Config, RawOrigin}; use sp_core::{Get, H256, U256}; +use sp_runtime::traits::{DispatchInfoOf, TransactionExtension}; use sp_runtime::{DispatchError, traits::TxBaseImplication}; use substrate_fixed::types::U96F32; use subtensor_runtime_common::{AlphaCurrency, Currency, SubnetInfo, TaoCurrency}; @@ -17,9 +19,9 @@ use subtensor_swap_interface::{OrderType, SwapHandler}; use super::mock; use super::mock::*; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; use crate::{Call, ColdkeySwapScheduleDuration, Error}; - // // SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_coldkey -- test_swap_total_hotkey_coldkey_stakes_this_interval --exact --nocapture // #[test] // fn test_swap_total_hotkey_coldkey_stakes_this_interval() { @@ -2245,9 +2247,9 @@ fn test_coldkey_in_swap_schedule_prevents_funds_usage() { assert!(ColdkeySwapScheduled::::contains_key(who)); // Setup the extension - let info: crate::DispatchInfo = - crate::DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorTransactionExtension::::new(); // Try each call @@ -2567,9 +2569,9 @@ fn test_coldkey_in_swap_schedule_prevents_critical_calls() { assert!(ColdkeySwapScheduled::::contains_key(who)); // Setup the extension - let info: crate::DispatchInfo = - crate::DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorTransactionExtension::::new(); // Try each call diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index 7befc1c668..00936e3a43 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -2,6 +2,7 @@ use ark_serialize::CanonicalDeserialize; use ark_serialize::CanonicalSerialize; +use frame_support::dispatch::DispatchInfo; use frame_support::{ assert_err, assert_ok, dispatch::{DispatchClass, DispatchResult, GetDispatchInfo, Pays}, @@ -13,6 +14,7 @@ use scale_info::prelude::collections::HashMap; use sha2::Digest; use sp_core::Encode; use sp_core::{Get, H256, U256}; +use sp_runtime::traits::{DispatchInfoOf, TransactionExtension}; use sp_runtime::{ BoundedVec, DispatchError, traits::{BlakeTwo256, ConstU32, Hash, TxBaseImplication}, @@ -32,8 +34,8 @@ use w3f_bls::EngineBLS; use super::mock; use super::mock::*; use crate::coinbase::reveal_commits::{LegacyWeightsTlockPayload, WeightsTlockPayload}; +use crate::transaction_extension::SubtensorTransactionExtension; use crate::*; - /*************************** pub fn set_weights() tests *****************************/ @@ -100,10 +102,10 @@ fn test_set_rootweights_validate() { // Verify stake is less than minimum assert!(SubtensorModule::get_total_stake_for_hotkey(&hotkey) < min_stake); - let info: crate::DispatchInfo = - crate::DispatchInfoOf::<::RuntimeCall>::default(); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let extension = SubtensorTransactionExtension::::new(); // Submit to the signed extension validate function let result_no_stake = extension.validate( RawOrigin::Signed(who).into(), @@ -250,8 +252,8 @@ fn test_commit_weights_validate() { SubtensorModule::set_stake_threshold(min_stake_with_slippage.to_u64() + 1); // Submit to the signed extension validate function - let info = crate::DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let info = DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorTransactionExtension::::new(); // Submit to the signed extension validate function let result_no_stake = extension.validate( RawOrigin::Signed(who).into(), @@ -371,10 +373,10 @@ fn test_set_weights_validate() { // Verify stake is less than minimum assert!(SubtensorModule::get_total_stake_for_hotkey(&hotkey) < min_stake); - let info: crate::DispatchInfo = - crate::DispatchInfoOf::<::RuntimeCall>::default(); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let extension = SubtensorTransactionExtension::::new(); // Submit to the signed extension validate function let result_no_stake = extension.validate( RawOrigin::Signed(who).into(), @@ -472,10 +474,10 @@ fn test_reveal_weights_validate() { // Verify stake is less than minimum assert!(SubtensorModule::get_total_stake_for_hotkey(&hotkey) < min_stake); - let info: crate::DispatchInfo = - crate::DispatchInfoOf::<::RuntimeCall>::default(); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let extension = SubtensorTransactionExtension::::new(); // Submit to the signed extension validate function let result_no_stake = extension.validate( RawOrigin::Signed(who).into(), @@ -654,9 +656,9 @@ fn test_batch_reveal_weights_validate() { // Set the minimum stake SubtensorModule::set_stake_threshold(min_stake.into()); - let info: crate::DispatchInfo = - crate::DispatchInfoOf::<::RuntimeCall>::default(); - let extension = crate::SubtensorTransactionExtension::::new(); + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorTransactionExtension::::new(); // Test 1: StakeAmountTooLow - Verify stake is less than minimum assert!(SubtensorModule::get_total_stake_for_hotkey(&hotkey) < min_stake); diff --git a/pallets/subtensor/src/transaction_extension.rs b/pallets/subtensor/src/transaction_extension.rs new file mode 100644 index 0000000000..deb42efabf --- /dev/null +++ b/pallets/subtensor/src/transaction_extension.rs @@ -0,0 +1,373 @@ +use crate::{ + BalancesCall, Call, ColdkeySwapScheduled, Config, CustomTransactionError, Error, Pallet, +}; +use codec::{Decode, DecodeWithMemTracking, Encode}; +use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; +use frame_support::pallet_prelude::Weight; +use frame_support::traits::IsSubType; +use scale_info::TypeInfo; +use sp_runtime::traits::{ + AsSystemOriginSigner, DispatchInfoOf, Dispatchable, Implication, TransactionExtension, + ValidateResult, +}; +use sp_runtime::transaction_validity::{ + TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, +}; +use sp_std::marker::PhantomData; +use sp_std::vec::Vec; +use subtensor_macros::freeze_struct; +use subtensor_runtime_common::NetUid; + +#[freeze_struct("2e02eb32e5cb25d3")] +#[derive(Default, Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] +pub struct SubtensorTransactionExtension(pub PhantomData); + +impl sp_std::fmt::Debug for SubtensorTransactionExtension { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "SubtensorTransactionExtension") + } +} + +impl SubtensorTransactionExtension +where + ::RuntimeCall: + Dispatchable, + ::RuntimeCall: IsSubType>, +{ + pub fn new() -> Self { + Self(Default::default()) + } + pub fn validity_ok(priority: u64) -> ValidTransaction { + ValidTransaction { + priority, + ..Default::default() + } + } + + pub fn check_weights_min_stake(who: &T::AccountId, netuid: NetUid) -> bool { + Pallet::::check_weights_min_stake(who, netuid) + } + + pub fn result_to_validity(result: Result<(), Error>, priority: u64) -> TransactionValidity { + if let Err(err) = result { + Err(match err { + Error::::AmountTooLow => CustomTransactionError::StakeAmountTooLow.into(), + Error::::SubnetNotExists => CustomTransactionError::SubnetDoesntExist.into(), + Error::::NotEnoughBalanceToStake => CustomTransactionError::BalanceTooLow.into(), + Error::::HotKeyAccountNotExists => { + CustomTransactionError::HotkeyAccountDoesntExist.into() + } + Error::::NotEnoughStakeToWithdraw => { + CustomTransactionError::NotEnoughStakeToWithdraw.into() + } + Error::::InsufficientLiquidity => { + CustomTransactionError::InsufficientLiquidity.into() + } + Error::::SlippageTooHigh => CustomTransactionError::SlippageTooHigh.into(), + Error::::TransferDisallowed => CustomTransactionError::TransferDisallowed.into(), + Error::::HotKeyNotRegisteredInNetwork => { + CustomTransactionError::HotKeyNotRegisteredInNetwork.into() + } + Error::::InvalidIpAddress => CustomTransactionError::InvalidIpAddress.into(), + Error::::ServingRateLimitExceeded => { + CustomTransactionError::ServingRateLimitExceeded.into() + } + Error::::InvalidPort => CustomTransactionError::InvalidPort.into(), + _ => CustomTransactionError::BadRequest.into(), + }) + } else { + Ok(ValidTransaction { + priority, + ..Default::default() + }) + } + } +} + +impl + TransactionExtension<::RuntimeCall> + for SubtensorTransactionExtension +where + ::RuntimeCall: + Dispatchable, + ::RuntimeOrigin: AsSystemOriginSigner + Clone, + ::RuntimeCall: IsSubType>, + ::RuntimeCall: IsSubType>, +{ + const IDENTIFIER: &'static str = "SubtensorTransactionExtension"; + + type Implicit = (); + type Val = Option; + type Pre = (); + + fn weight(&self, _call: &::RuntimeCall) -> Weight { + // TODO: benchmark transaction extension + Weight::zero() + } + + fn validate( + &self, + origin: ::RuntimeOrigin, + call: &::RuntimeCall, + _info: &DispatchInfoOf<::RuntimeCall>, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Implication, + _source: TransactionSource, + ) -> ValidateResult::RuntimeCall> { + // Ensure the transaction is signed, else we just skip the extension. + let Some(who) = origin.as_system_origin_signer() else { + return Ok((Default::default(), None, origin)); + }; + + match call.is_sub_type() { + Some(Call::commit_weights { netuid, .. }) => { + if Self::check_weights_min_stake(who, *netuid) { + Ok((Default::default(), Some(who.clone()), origin)) + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::reveal_weights { + netuid, + uids, + values, + salt, + version_key, + }) => { + if Self::check_weights_min_stake(who, *netuid) { + let provided_hash = Pallet::::get_commit_hash( + who, + *netuid, + uids, + values, + salt, + *version_key, + ); + match Pallet::::find_commit_block_via_hash(provided_hash) { + Some(commit_block) => { + if Pallet::::is_reveal_block_range(*netuid, commit_block) { + Ok((Default::default(), Some(who.clone()), origin)) + } else { + Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) + } + } + None => Err(CustomTransactionError::CommitNotFound.into()), + } + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::batch_reveal_weights { + netuid, + uids_list, + values_list, + salts_list, + version_keys, + }) => { + if Self::check_weights_min_stake(who, *netuid) { + let num_reveals = uids_list.len(); + if num_reveals == values_list.len() + && num_reveals == salts_list.len() + && num_reveals == version_keys.len() + { + let provided_hashes = (0..num_reveals) + .map(|i| { + Pallet::::get_commit_hash( + who, + *netuid, + uids_list.get(i).unwrap_or(&Vec::new()), + values_list.get(i).unwrap_or(&Vec::new()), + salts_list.get(i).unwrap_or(&Vec::new()), + *version_keys.get(i).unwrap_or(&0_u64), + ) + }) + .collect::>(); + + let batch_reveal_block = provided_hashes + .iter() + .filter_map(|hash| Pallet::::find_commit_block_via_hash(*hash)) + .collect::>(); + + if provided_hashes.len() == batch_reveal_block.len() { + if Pallet::::is_batch_reveal_block_range(*netuid, batch_reveal_block) + { + Ok((Default::default(), Some(who.clone()), origin)) + } else { + Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) + } + } else { + Err(CustomTransactionError::CommitNotFound.into()) + } + } else { + Err(CustomTransactionError::InputLengthsUnequal.into()) + } + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::set_weights { netuid, .. }) => { + if Self::check_weights_min_stake(who, *netuid) { + Ok((Default::default(), Some(who.clone()), origin)) + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::set_tao_weights { netuid, hotkey, .. }) => { + if Self::check_weights_min_stake(hotkey, *netuid) { + Ok((Default::default(), Some(who.clone()), origin)) + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::commit_crv3_weights { + netuid, + reveal_round, + .. + }) => { + if Self::check_weights_min_stake(who, *netuid) { + if *reveal_round < pallet_drand::LastStoredRound::::get() { + return Err(CustomTransactionError::InvalidRevealRound.into()); + } + Ok((Default::default(), Some(who.clone()), origin)) + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::commit_timelocked_weights { + netuid, + reveal_round, + .. + }) => { + if Self::check_weights_min_stake(who, *netuid) { + if *reveal_round < pallet_drand::LastStoredRound::::get() { + return Err(CustomTransactionError::InvalidRevealRound.into()); + } + Ok((Default::default(), Some(who.clone()), origin)) + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::add_stake { .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::add_stake_limit { .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::remove_stake { .. }) => Ok((Default::default(), Some(who.clone()), origin)), + Some(Call::remove_stake_limit { .. }) => { + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::move_stake { .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::transfer_stake { .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::swap_stake { .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::swap_stake_limit { .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::register { netuid, .. } | Call::burned_register { netuid, .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + + let registrations_this_interval = + Pallet::::get_registrations_this_interval(*netuid); + let max_registrations_per_interval = + Pallet::::get_target_registrations_per_interval(*netuid); + if registrations_this_interval >= (max_registrations_per_interval.saturating_mul(3)) + { + // If the registration limit for the interval is exceeded, reject the transaction + return Err(CustomTransactionError::RateLimitExceeded.into()); + } + + Ok((Default::default(), Some(who.clone()), origin)) + } + Some(Call::dissolve_network { .. }) => { + if ColdkeySwapScheduled::::contains_key(who) { + Err(CustomTransactionError::ColdkeyInSwapSchedule.into()) + } else { + Ok((Default::default(), Some(who.clone()), origin)) + } + } + Some(Call::serve_axon { + netuid, + version, + ip, + port, + ip_type, + protocol, + placeholder1, + placeholder2, + }) => { + // Fully validate the user input + Self::result_to_validity( + Pallet::::validate_serve_axon( + who, + *netuid, + *version, + *ip, + *port, + *ip_type, + *protocol, + *placeholder1, + *placeholder2, + ), + 0u64, + ) + .map(|validity| (validity, Some(who.clone()), origin.clone())) + } + _ => { + if let Some( + BalancesCall::transfer_keep_alive { .. } + | BalancesCall::transfer_all { .. } + | BalancesCall::transfer_allow_death { .. }, + ) = call.is_sub_type() + { + if ColdkeySwapScheduled::::contains_key(who) { + return Err(CustomTransactionError::ColdkeyInSwapSchedule.into()); + } + } + + Ok((Default::default(), Some(who.clone()), origin)) + } + } + } + + // NOTE: Add later when we put in a pre and post dispatch step. + fn prepare( + self, + _val: Self::Val, + _origin: &::RuntimeOrigin, + _call: &::RuntimeCall, + _info: &DispatchInfoOf<::RuntimeCall>, + _len: usize, + ) -> Result { + Ok(()) + } +} diff --git a/pallets/utility/src/lib.rs b/pallets/utility/src/lib.rs index 294836677d..8ee888889e 100644 --- a/pallets/utility/src/lib.rs +++ b/pallets/utility/src/lib.rs @@ -190,9 +190,9 @@ pub mod pallet { /// event is deposited. #[pallet::call_index(0)] #[pallet::weight({ - let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(calls); + let dispatch_weight = Pallet::::weight(calls); let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch(calls.len() as u32)); - (dispatch_weight, dispatch_class) + (dispatch_weight, DispatchClass::Normal) })] pub fn batch( origin: OriginFor, @@ -302,9 +302,9 @@ pub mod pallet { /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(2)] #[pallet::weight({ - let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(calls); + let dispatch_weight = Pallet::::weight(calls); let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::batch_all(calls.len() as u32)); - (dispatch_weight, dispatch_class) + (dispatch_weight, DispatchClass::Normal) })] pub fn batch_all( origin: OriginFor, @@ -401,9 +401,9 @@ pub mod pallet { /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(4)] #[pallet::weight({ - let (dispatch_weight, dispatch_class) = Pallet::::weight_and_dispatch_class(calls); + let dispatch_weight = Pallet::::weight(calls); let dispatch_weight = dispatch_weight.saturating_add(T::WeightInfo::force_batch(calls.len() as u32)); - (dispatch_weight, dispatch_class) + (dispatch_weight, DispatchClass::Normal) })] pub fn force_batch( origin: OriginFor, @@ -474,29 +474,15 @@ pub mod pallet { impl Pallet { /// Get the accumulated `weight` and the dispatch class for the given `calls`. - fn weight_and_dispatch_class( - calls: &[::RuntimeCall], - ) -> (Weight, DispatchClass) { + fn weight(calls: &[::RuntimeCall]) -> Weight { let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()); - let (dispatch_weight, dispatch_class) = dispatch_infos.fold( - (Weight::zero(), DispatchClass::Operational), - |(total_weight, dispatch_class), di| { - ( - if di.pays_fee == Pays::Yes { - total_weight.saturating_add(di.call_weight) - } else { - total_weight - }, - if di.class == DispatchClass::Normal { - di.class - } else { - dispatch_class - }, - ) - }, - ); - - (dispatch_weight, dispatch_class) + dispatch_infos.fold(Weight::zero(), |total_weight, di| { + if di.pays_fee == Pays::Yes { + total_weight.saturating_add(di.call_weight) + } else { + total_weight + } + }) } } } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 16af31170e..8c0c56b02f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -12,6 +12,7 @@ use core::num::NonZeroU64; pub mod check_nonce; mod migrations; +pub mod transaction_payment_wrapper; extern crate alloc; @@ -147,8 +148,12 @@ impl frame_system::offchain::CreateSignedTransaction frame_system::CheckEra::::from(Era::Immortal), check_nonce::CheckNonce::::from(nonce).into(), frame_system::CheckWeight::::new(), - pallet_transaction_payment::ChargeTransactionPayment::::from(0), - pallet_subtensor::SubtensorTransactionExtension::::new(), + ChargeTransactionPaymentWrapper::new( + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ), + pallet_subtensor::transaction_extension::SubtensorTransactionExtension::::new( + ), + pallet_drand::drand_priority::DrandPriority::::new(), frame_metadata_hash_extension::CheckMetadataHash::::new(true), ); @@ -1256,6 +1261,7 @@ impl pallet_subtensor_swap::Config for Runtime { type WeightInfo = pallet_subtensor_swap::weights::DefaultWeight; } +use crate::transaction_payment_wrapper::ChargeTransactionPaymentWrapper; use sp_runtime::BoundedVec; pub struct AuraPalletIntrf; @@ -1624,8 +1630,9 @@ pub type TransactionExtensions = ( frame_system::CheckEra, check_nonce::CheckNonce, frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - pallet_subtensor::SubtensorTransactionExtension, + ChargeTransactionPaymentWrapper, + pallet_subtensor::transaction_extension::SubtensorTransactionExtension, + pallet_drand::drand_priority::DrandPriority, frame_metadata_hash_extension::CheckMetadataHash, ); diff --git a/runtime/src/transaction_payment_wrapper.rs b/runtime/src/transaction_payment_wrapper.rs new file mode 100644 index 0000000000..f299c52497 --- /dev/null +++ b/runtime/src/transaction_payment_wrapper.rs @@ -0,0 +1,161 @@ +use crate::Weight; +use codec::{Decode, DecodeWithMemTracking, Encode}; +use frame_election_provider_support::private::sp_arithmetic::traits::SaturatedConversion; +use frame_support::dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo}; +use frame_support::pallet_prelude::TypeInfo; +use pallet_transaction_payment::{ChargeTransactionPayment, Config, Pre, Val}; +use sp_runtime::DispatchResult; +use sp_runtime::traits::{ + DispatchInfoOf, DispatchOriginOf, Dispatchable, Implication, PostDispatchInfoOf, + TransactionExtension, TransactionExtensionMetadata, ValidateResult, +}; +use sp_runtime::transaction_validity::{ + TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, +}; +use sp_std::vec::Vec; +use subtensor_macros::freeze_struct; + +#[freeze_struct("5f10cb9db06873c0")] +#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct ChargeTransactionPaymentWrapper { + charge_transaction_payment: ChargeTransactionPayment, +} + +impl core::fmt::Debug for ChargeTransactionPaymentWrapper { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "ChargeTransactionPaymentWrapper",) + } + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result { + Ok(()) + } +} + +impl ChargeTransactionPaymentWrapper { + pub fn new(charge_transaction_payment: ChargeTransactionPayment) -> Self { + Self { + charge_transaction_payment, + } + } +} + +impl TransactionExtension for ChargeTransactionPaymentWrapper +where + T::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "ChargeTransactionPaymentWrapper"; + type Implicit = (); + type Val = Val; + type Pre = Pre; + + fn weight(&self, call: &T::RuntimeCall) -> Weight { + self.charge_transaction_payment.weight(call) + } + + fn validate( + &self, + origin: DispatchOriginOf, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + self_implicit: Self::Implicit, + inherited_implication: &impl Implication, + source: TransactionSource, + ) -> ValidateResult { + let inner_validate = self.charge_transaction_payment.validate( + origin, + call, + info, + len, + self_implicit, + inherited_implication, + source, + ); + + match inner_validate { + Ok((mut valid_transaction, val, origin)) => { + let overridden_priority = { + match info.class { + DispatchClass::Normal => 1u64, + DispatchClass::Mandatory => { + // Mandatory extrinsics should be prohibited (e.g. by the [`CheckWeight`] + // extensions), but just to be safe let's return the same priority as `Normal` here. + 1u64 + } + DispatchClass::Operational => { + // System calls + 10_000_000_000u64 + } + } + .saturated_into::() + }; + + valid_transaction.priority = overridden_priority; + + Ok((valid_transaction, val, origin)) + } + Err(err) => Err(err), + } + } + + fn prepare( + self, + val: Self::Val, + origin: &DispatchOriginOf, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.charge_transaction_payment + .prepare(val, origin, call, info, len) + } + fn metadata() -> Vec { + ChargeTransactionPayment::::metadata() + } + fn post_dispatch_details( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result { + ChargeTransactionPayment::::post_dispatch_details(pre, info, post_info, len, result) + } + + fn post_dispatch( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + ChargeTransactionPayment::::post_dispatch(pre, info, post_info, len, result) + } + + fn bare_validate( + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> TransactionValidity { + ChargeTransactionPayment::::bare_validate(call, info, len) + } + + fn bare_validate_and_prepare( + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result<(), TransactionValidityError> { + ChargeTransactionPayment::::bare_validate_and_prepare(call, info, len) + } + + fn bare_post_dispatch( + info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + ChargeTransactionPayment::::bare_post_dispatch(info, post_info, len, result) + } +}