From 24db024171f4d02e7b8ab6f28b9fc82d08b5a57b Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 3 Sep 2025 15:00:53 +0300 Subject: [PATCH 1/8] Add rate-limited origin checks --- pallets/subtensor/src/lib.rs | 24 ++++++ pallets/subtensor/src/macros/errors.rs | 2 + pallets/subtensor/src/utils/misc.rs | 106 ++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 4c2eaf0cc7..6efd938608 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -867,10 +867,32 @@ pub mod pallet { 50400 } + #[pallet::type_value] + /// Default value for subnet owner hyperparameter update rate limit (in blocks) + pub fn DefaultOwnerHyperparamRateLimit() -> u64 { + 0 + } + + #[pallet::type_value] + /// Default number of terminal blocks in a tempo during which admin operations are prohibited + pub fn DefaultAdminFreezeWindow() -> u16 { + 10 + } + #[pallet::storage] pub type MinActivityCutoff = StorageValue<_, u16, ValueQuery, DefaultMinActivityCutoff>; + #[pallet::storage] + /// Global window (in blocks) at the end of each tempo where admin ops are disallowed + pub type AdminFreezeWindow = + StorageValue<_, u16, ValueQuery, DefaultAdminFreezeWindow>; + + #[pallet::storage] + /// Global rate limit (in blocks) for subnet owner hyperparameter updates + pub type OwnerHyperparamRateLimit = + StorageValue<_, u64, ValueQuery, DefaultOwnerHyperparamRateLimit>; + #[pallet::storage] pub type ColdkeySwapScheduleDuration = StorageValue<_, BlockNumberFor, ValueQuery, DefaultColdkeySwapScheduleDuration>; @@ -2138,6 +2160,8 @@ impl> pub enum RateLimitKey { // The setting sn owner hotkey operation is rate limited per netuid SetSNOwnerHotkey(NetUid), + // Generic rate limit for subnet-owner hyperparameter updates (per netuid) + OwnerHyperparamUpdate(NetUid), } pub trait ProxyInterface { diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index e6d9c231d1..ed6ca3c002 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -238,6 +238,8 @@ mod errors { BeneficiaryDoesNotOwnHotkey, /// Expected beneficiary origin. ExpectedBeneficiaryOrigin, + /// Admin operation is prohibited during the protected weights window + AdminActionProhibitedDuringWeightsWindow, /// Symbol does not exist. SymbolDoesNotExist, /// Symbol already in use. diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index f64962f094..b7f3e1288e 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -1,6 +1,6 @@ use super::*; use crate::{ - Error, + Error, RateLimitKey, system::{ensure_root, ensure_signed, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, }; use safe_math::*; @@ -33,6 +33,110 @@ impl Pallet { } } + /// Like `ensure_root` but also prohibits calls during the last N blocks of the tempo. + pub fn ensure_root_with_rate_limit( + o: T::RuntimeOrigin, + netuid: NetUid, + ) -> Result<(), DispatchError> { + ensure_root(o)?; + let now = Self::get_current_block_as_u64(); + Self::ensure_not_in_admin_freeze_window(netuid, now)?; + Ok(()) + } + + /// Like `ensure_subnet_owner` but also checks transaction rate limits. + pub fn ensure_sn_owner_with_rate_limit( + o: T::RuntimeOrigin, + netuid: NetUid, + ) -> Result<(), DispatchError> { + Self::ensure_subnet_owner(o, netuid)?; + let now = Self::get_current_block_as_u64(); + // Disallow inside freeze window and enforce owner hyperparam rate limit + Self::ensure_not_in_admin_freeze_window(netuid, now)?; + Self::ensure_owner_hparam_rate_limit(netuid, now)?; + Ok(()) + } + + /// Like `ensure_subnet_owner_or_root` but also checks transaction rate limits. + /// Root is not rate-limited outside the freeze window, but is also prohibited inside it. + pub fn ensure_sn_owner_or_root_with_rate_limit( + o: T::RuntimeOrigin, + netuid: NetUid, + ) -> Result<(), DispatchError> { + let now = Self::get_current_block_as_u64(); + + // If root, only enforce freeze window. + if ensure_root(o.clone()).is_ok() { + Self::ensure_not_in_admin_freeze_window(netuid, now)?; + return Ok(()); + } + + // Otherwise ensure subnet owner and apply both checks. + Self::ensure_subnet_owner(o, netuid)?; + Self::ensure_not_in_admin_freeze_window(netuid, now)?; + Self::ensure_owner_hparam_rate_limit(netuid, now)?; + + Ok(()) + } + + /// Returns true if the current block is within the terminal freeze window of the tempo for the + /// given subnet. During this window, admin ops are prohibited to avoid interference with + /// validator weight submissions. + pub fn is_in_admin_freeze_window(netuid: NetUid, current_block: u64) -> bool { + let tempo = Self::get_tempo(netuid); + if tempo == 0 { + return false; + } + let remaining = Self::blocks_until_next_epoch(netuid, tempo, current_block); + let window = AdminFreezeWindow::::get() as u64; + remaining < window + } + + fn ensure_not_in_admin_freeze_window(netuid: NetUid, now: u64) -> Result<(), DispatchError> { + ensure!( + !Self::is_in_admin_freeze_window(netuid, now), + Error::::AdminActionProhibitedDuringWeightsWindow + ); + Ok(()) + } + + fn ensure_owner_hparam_rate_limit(netuid: NetUid, now: u64) -> Result<(), DispatchError> { + let limit = OwnerHyperparamRateLimit::::get(); + if limit > 0 { + let last = + Self::get_rate_limited_last_block(&RateLimitKey::OwnerHyperparamUpdate(netuid)); + ensure!( + now.saturating_sub(last) >= limit || last == 0, + Error::::TxRateLimitExceeded + ); + } + Ok(()) + } + + // === Admin freeze window accessors === + pub fn get_admin_freeze_window() -> u16 { + AdminFreezeWindow::::get() + } + + pub fn set_admin_freeze_window(window: u16) { + AdminFreezeWindow::::set(window); + } + + /// Helper to be called after a successful owner hyperparameter update. + /// Records the current block against the OwnerHyperparamUpdate rate limit key. + pub fn mark_owner_hyperparam_update(netuid: NetUid) { + let now = Self::get_current_block_as_u64(); + Self::set_rate_limited_last_block(&RateLimitKey::OwnerHyperparamUpdate(netuid), now); + } + + pub fn get_owner_hyperparam_rate_limit() -> u64 { + OwnerHyperparamRateLimit::::get() + } + + pub fn set_owner_hyperparam_rate_limit(limit: u64) { + OwnerHyperparamRateLimit::::set(limit); + } + // ======================== // ==== Global Setters ==== // ======================== From c8d5ebe58ef118e1d8ea372855325020318a86d1 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 3 Sep 2025 15:24:41 +0300 Subject: [PATCH 2/8] Apply rate limits for hyperparams set --- pallets/admin-utils/src/lib.rs | 351 +++++++++++++++---- pallets/subtensor/src/macros/events.rs | 4 + pallets/subtensor/src/utils/misc.rs | 91 +++-- pallets/subtensor/src/utils/rate_limiting.rs | 12 + 4 files changed, 348 insertions(+), 110 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index abc5e7a443..e8d3827966 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -22,10 +22,7 @@ pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_support::traits::tokens::Balance; - use frame_support::{ - dispatch::{DispatchResult, RawOrigin}, - pallet_prelude::StorageMap, - }; + use frame_support::{dispatch::DispatchResult, pallet_prelude::StorageMap}; use frame_system::pallet_prelude::*; use pallet_evm_chain_id::{self, ChainId}; use pallet_subtensor::utils::rate_limiting::TransactionType; @@ -214,10 +211,18 @@ pub mod pallet { netuid: NetUid, serving_rate_limit: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; - + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; pallet_subtensor::Pallet::::set_serving_rate_limit(netuid, serving_rate_limit); log::debug!("ServingRateLimitSet( serving_rate_limit: {serving_rate_limit:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -233,7 +238,7 @@ pub mod pallet { netuid: NetUid, min_difficulty: u64, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -258,7 +263,11 @@ pub mod pallet { netuid: NetUid, max_difficulty: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -268,6 +277,11 @@ pub mod pallet { log::debug!( "MaxDifficultySet( netuid: {netuid:?} max_difficulty: {max_difficulty:?} ) " ); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -283,34 +297,28 @@ pub mod pallet { netuid: NetUid, weights_version_key: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin.clone(), + netuid, + &[ + TransactionType::OwnerHyperparamUpdate, + TransactionType::SetWeightsVersionKey, + ], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - if let Ok(RawOrigin::Signed(who)) = origin.into() { - // SN Owner - // Ensure the origin passes the rate limit. - ensure!( - pallet_subtensor::Pallet::::passes_rate_limit_on_subnet( - &TransactionType::SetWeightsVersionKey, - &who, - netuid, - ), - pallet_subtensor::Error::::TxRateLimitExceeded - ); - - // Set last transaction block - let current_block = pallet_subtensor::Pallet::::get_current_block_as_u64(); - pallet_subtensor::Pallet::::set_last_transaction_block_on_subnet( - &who, - netuid, - &TransactionType::SetWeightsVersionKey, - current_block, - ); - } + Self::record_owner_rl( + maybe_owner, + netuid, + &[ + TransactionType::OwnerHyperparamUpdate, + TransactionType::SetWeightsVersionKey, + ], + ); pallet_subtensor::Pallet::::set_weights_version_key(netuid, weights_version_key); log::debug!( @@ -388,13 +396,22 @@ pub mod pallet { netuid: NetUid, adjustment_alpha: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); pallet_subtensor::Pallet::::set_adjustment_alpha(netuid, adjustment_alpha); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); log::debug!("AdjustmentAlphaSet( adjustment_alpha: {adjustment_alpha:?} ) "); Ok(()) } @@ -411,13 +428,22 @@ pub mod pallet { netuid: NetUid, max_weight_limit: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); pallet_subtensor::Pallet::::set_max_weight_limit(netuid, max_weight_limit); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); log::debug!( "MaxWeightLimitSet( netuid: {netuid:?} max_weight_limit: {max_weight_limit:?} ) " ); @@ -436,13 +462,22 @@ pub mod pallet { netuid: NetUid, immunity_period: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); pallet_subtensor::Pallet::::set_immunity_period(netuid, immunity_period); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); log::debug!( "ImmunityPeriodSet( netuid: {netuid:?} immunity_period: {immunity_period:?} ) " ); @@ -461,7 +496,11 @@ pub mod pallet { netuid: NetUid, min_allowed_weights: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -471,6 +510,11 @@ pub mod pallet { log::debug!( "MinAllowedWeightSet( netuid: {netuid:?} min_allowed_weights: {min_allowed_weights:?} ) " ); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -510,7 +554,11 @@ pub mod pallet { .saturating_add(::DbWeight::get().reads(1_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_set_kappa(origin: OriginFor, netuid: NetUid, kappa: u16) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -518,6 +566,11 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_kappa(netuid, kappa); log::debug!("KappaSet( netuid: {netuid:?} kappa: {kappa:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -529,7 +582,11 @@ pub mod pallet { .saturating_add(::DbWeight::get().reads(1_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_set_rho(origin: OriginFor, netuid: NetUid, rho: u16) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -537,6 +594,11 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_rho(netuid, rho); log::debug!("RhoSet( netuid: {netuid:?} rho: {rho:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -552,7 +614,11 @@ pub mod pallet { netuid: NetUid, activity_cutoff: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -568,6 +634,11 @@ pub mod pallet { log::debug!( "ActivityCutoffSet( netuid: {netuid:?} activity_cutoff: {activity_cutoff:?} ) " ); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -613,7 +684,11 @@ pub mod pallet { netuid: NetUid, registration_allowed: bool, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; pallet_subtensor::Pallet::::set_network_pow_registration_allowed( netuid, @@ -622,6 +697,11 @@ pub mod pallet { log::debug!( "NetworkPowRegistrationAllowed( registration_allowed: {registration_allowed:?} ) " ); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -637,7 +717,7 @@ pub mod pallet { netuid: NetUid, target_registrations_per_interval: u16, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -665,7 +745,7 @@ pub mod pallet { netuid: NetUid, min_burn: TaoCurrency, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -688,7 +768,7 @@ pub mod pallet { netuid: NetUid, max_burn: TaoCurrency, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -711,7 +791,7 @@ pub mod pallet { netuid: NetUid, difficulty: u64, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -733,7 +813,7 @@ pub mod pallet { netuid: NetUid, max_allowed_validators: u16, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -766,9 +846,12 @@ pub mod pallet { netuid: NetUid, bonds_moving_average: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; - - if pallet_subtensor::Pallet::::ensure_subnet_owner(origin, netuid).is_ok() { + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; + if maybe_owner.is_some() { ensure!( bonds_moving_average <= 975000, Error::::BondsMovingAverageMaxReached @@ -783,6 +866,11 @@ pub mod pallet { log::debug!( "BondsMovingAverageSet( netuid: {netuid:?} bonds_moving_average: {bonds_moving_average:?} ) " ); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -798,7 +886,11 @@ pub mod pallet { netuid: NetUid, bonds_penalty: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -806,6 +898,11 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_bonds_penalty(netuid, bonds_penalty); log::debug!("BondsPenalty( netuid: {netuid:?} bonds_penalty: {bonds_penalty:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -821,7 +918,7 @@ pub mod pallet { netuid: NetUid, max_registrations_per_block: u16, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -885,7 +982,7 @@ pub mod pallet { .saturating_add(::DbWeight::get().reads(1_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_set_tempo(origin: OriginFor, netuid: NetUid, tempo: u16) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -1073,7 +1170,11 @@ pub mod pallet { netuid: NetUid, enabled: bool, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -1082,6 +1183,11 @@ pub mod pallet { pallet_subtensor::Pallet::::set_commit_reveal_weights_enabled(netuid, enabled); log::debug!("ToggleSetWeightsCommitReveal( netuid: {netuid:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -1101,9 +1207,18 @@ pub mod pallet { netuid: NetUid, enabled: bool, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; pallet_subtensor::Pallet::::set_liquid_alpha_enabled(netuid, enabled); log::debug!("LiquidAlphaEnableToggled( netuid: {netuid:?}, Enabled: {enabled:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -1116,10 +1231,22 @@ pub mod pallet { alpha_low: u16, alpha_high: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; - pallet_subtensor::Pallet::::do_set_alpha_values( + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin.clone(), + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; + let res = pallet_subtensor::Pallet::::do_set_alpha_values( origin, netuid, alpha_low, alpha_high, - ) + ); + if res.is_ok() { + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); + } + res } /// Sets the duration of the coldkey swap schedule. @@ -1211,7 +1338,11 @@ pub mod pallet { netuid: NetUid, interval: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -1221,6 +1352,11 @@ pub mod pallet { log::debug!("SetWeightCommitInterval( netuid: {netuid:?}, interval: {interval:?} ) "); pallet_subtensor::Pallet::::set_reveal_period(netuid, interval)?; + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -1294,8 +1430,20 @@ pub mod pallet { netuid: NetUid, toggle: bool, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; - pallet_subtensor::Pallet::::toggle_transfer(netuid, toggle) + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; + let res = pallet_subtensor::Pallet::::toggle_transfer(netuid, toggle); + if res.is_ok() { + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); + } + res } /// Toggles the enablement of an EVM precompile. @@ -1424,7 +1572,11 @@ pub mod pallet { netuid: NetUid, steepness: i16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin.clone(), + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), @@ -1440,6 +1592,11 @@ pub mod pallet { pallet_subtensor::Pallet::::set_alpha_sigmoid_steepness(netuid, steepness); log::debug!("AlphaSigmoidSteepnessSet( netuid: {netuid:?}, steepness: {steepness:?} )"); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -1459,11 +1616,20 @@ pub mod pallet { netuid: NetUid, enabled: bool, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; pallet_subtensor::Pallet::::set_yuma3_enabled(netuid, enabled); Self::deposit_event(Event::Yuma3EnableToggled { netuid, enabled }); log::debug!("Yuma3EnableToggled( netuid: {netuid:?}, Enabled: {enabled:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -1483,11 +1649,20 @@ pub mod pallet { netuid: NetUid, enabled: bool, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; pallet_subtensor::Pallet::::set_bonds_reset(netuid, enabled); Self::deposit_event(Event::BondsResetToggled { netuid, enabled }); log::debug!("BondsResetToggled( netuid: {netuid:?} bonds_reset: {enabled:?} ) "); + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); Ok(()) } @@ -1550,7 +1725,7 @@ pub mod pallet { netuid: NetUid, subtoken_enabled: bool, ) -> DispatchResult { - ensure_root(origin)?; + pallet_subtensor::Pallet::::ensure_root_with_rate_limit(origin, netuid)?; pallet_subtensor::SubtokenEnabled::::set(netuid, subtoken_enabled); log::debug!( @@ -1587,10 +1762,62 @@ pub mod pallet { netuid: NetUid, immune_neurons: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + )?; pallet_subtensor::Pallet::::set_owner_immune_neuron_limit(netuid, immune_neurons)?; + Self::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ); + Ok(()) + } + + /// Sets the admin freeze window length (in blocks) at the end of a tempo. + /// Only callable by root. + #[pallet::call_index(73)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_admin_freeze_window(origin: OriginFor, window: u16) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_admin_freeze_window(window); + log::debug!("AdminFreezeWindowSet( window: {window:?} ) "); Ok(()) } + + /// Sets the owner hyperparameter rate limit (in blocks). + /// Only callable by root. + #[pallet::call_index(74)] + #[pallet::weight((0, DispatchClass::Operational, Pays::No))] + pub fn sudo_set_owner_hparam_rate_limit( + origin: OriginFor, + limit: u64, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_owner_hyperparam_rate_limit(limit); + log::debug!("OwnerHyperparamRateLimitSet( limit: {limit:?} ) "); + Ok(()) + } + } + + impl Pallet { + // Helper: if owner path, record last-blocks for the provided TransactionTypes + fn record_owner_rl( + maybe_owner: Option<::AccountId>, + netuid: NetUid, + txs: &[TransactionType], + ) { + if let Some(who) = maybe_owner { + let now = pallet_subtensor::Pallet::::get_current_block_as_u64(); + for tx in txs { + pallet_subtensor::Pallet::::set_last_transaction_block_on_subnet( + &who, netuid, tx, now, + ); + } + } + } } } diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 2fab5ecdb4..4fdff241b2 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -114,6 +114,10 @@ mod events { TxDelegateTakeRateLimitSet(u64), /// setting the childkey take transaction rate limit. TxChildKeyTakeRateLimitSet(u64), + /// setting the admin freeze window length (last N blocks of tempo) + AdminFreezeWindowSet(u16), + /// setting the owner hyperparameter rate limit (in blocks) + OwnerHyperparamRateLimitSet(u64), /// minimum childkey take set MinChildKeyTakeSet(u16), /// maximum childkey take set diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index b7f3e1288e..14b617a0e0 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -1,6 +1,6 @@ use super::*; use crate::{ - Error, RateLimitKey, + Error, system::{ensure_root, ensure_signed, ensure_signed_or_root, pallet_prelude::BlockNumberFor}, }; use safe_math::*; @@ -14,20 +14,23 @@ impl Pallet { pub fn ensure_subnet_owner_or_root( o: T::RuntimeOrigin, netuid: NetUid, - ) -> Result<(), DispatchError> { + ) -> Result, DispatchError> { let coldkey = ensure_signed_or_root(o); match coldkey { - Ok(Some(who)) if SubnetOwner::::get(netuid) == who => Ok(()), + Ok(Some(who)) if SubnetOwner::::get(netuid) == who => Ok(Some(who)), Ok(Some(_)) => Err(DispatchError::BadOrigin), - Ok(None) => Ok(()), + Ok(None) => Ok(None), Err(x) => Err(x.into()), } } - pub fn ensure_subnet_owner(o: T::RuntimeOrigin, netuid: NetUid) -> Result<(), DispatchError> { + pub fn ensure_subnet_owner( + o: T::RuntimeOrigin, + netuid: NetUid, + ) -> Result { let coldkey = ensure_signed(o); match coldkey { - Ok(who) if SubnetOwner::::get(netuid) == who => Ok(()), + Ok(who) if SubnetOwner::::get(netuid) == who => Ok(who), Ok(_) => Err(DispatchError::BadOrigin), Err(x) => Err(x.into()), } @@ -44,39 +47,47 @@ impl Pallet { Ok(()) } - /// Like `ensure_subnet_owner` but also checks transaction rate limits. - pub fn ensure_sn_owner_with_rate_limit( + /// Ensure owner-or-root with a set of TransactionType rate checks (owner only). + /// - Root: only freeze window is enforced; no TransactionType checks. + /// - Owner (Signed): freeze window plus all rate checks in `limits` using signer extracted from + /// origin. + pub fn ensure_sn_owner_or_root_with_limits( o: T::RuntimeOrigin, netuid: NetUid, - ) -> Result<(), DispatchError> { - Self::ensure_subnet_owner(o, netuid)?; + limits: &[crate::utils::rate_limiting::TransactionType], + ) -> Result, DispatchError> { + let maybe_who = Self::ensure_subnet_owner_or_root(o, netuid)?; let now = Self::get_current_block_as_u64(); - // Disallow inside freeze window and enforce owner hyperparam rate limit Self::ensure_not_in_admin_freeze_window(netuid, now)?; - Self::ensure_owner_hparam_rate_limit(netuid, now)?; - Ok(()) + if let Some(who) = maybe_who.as_ref() { + for tx in limits.iter() { + ensure!( + Self::passes_rate_limit_on_subnet(tx, who, netuid), + Error::::TxRateLimitExceeded + ); + } + } + Ok(maybe_who) } - /// Like `ensure_subnet_owner_or_root` but also checks transaction rate limits. - /// Root is not rate-limited outside the freeze window, but is also prohibited inside it. - pub fn ensure_sn_owner_or_root_with_rate_limit( + /// Ensure the caller is the subnet owner and passes all provided rate limits. + /// This does NOT allow root; it is strictly owner-only. + /// Returns the signer (owner) on success so callers may record last-blocks. + pub fn ensure_sn_owner_with_limits( o: T::RuntimeOrigin, netuid: NetUid, - ) -> Result<(), DispatchError> { + limits: &[crate::utils::rate_limiting::TransactionType], + ) -> Result { + let who = Self::ensure_subnet_owner(o, netuid)?; let now = Self::get_current_block_as_u64(); - - // If root, only enforce freeze window. - if ensure_root(o.clone()).is_ok() { - Self::ensure_not_in_admin_freeze_window(netuid, now)?; - return Ok(()); - } - - // Otherwise ensure subnet owner and apply both checks. - Self::ensure_subnet_owner(o, netuid)?; Self::ensure_not_in_admin_freeze_window(netuid, now)?; - Self::ensure_owner_hparam_rate_limit(netuid, now)?; - - Ok(()) + for tx in limits.iter() { + ensure!( + Self::passes_rate_limit_on_subnet(tx, &who, netuid), + Error::::TxRateLimitExceeded + ); + } + Ok(who) } /// Returns true if the current block is within the terminal freeze window of the tempo for the @@ -100,18 +111,7 @@ impl Pallet { Ok(()) } - fn ensure_owner_hparam_rate_limit(netuid: NetUid, now: u64) -> Result<(), DispatchError> { - let limit = OwnerHyperparamRateLimit::::get(); - if limit > 0 { - let last = - Self::get_rate_limited_last_block(&RateLimitKey::OwnerHyperparamUpdate(netuid)); - ensure!( - now.saturating_sub(last) >= limit || last == 0, - Error::::TxRateLimitExceeded - ); - } - Ok(()) - } + // (Removed dedicated ensure_owner_hparam_rate_limit; OwnerHyperparamUpdate is checked via TransactionType) // === Admin freeze window accessors === pub fn get_admin_freeze_window() -> u16 { @@ -120,13 +120,7 @@ impl Pallet { pub fn set_admin_freeze_window(window: u16) { AdminFreezeWindow::::set(window); - } - - /// Helper to be called after a successful owner hyperparameter update. - /// Records the current block against the OwnerHyperparamUpdate rate limit key. - pub fn mark_owner_hyperparam_update(netuid: NetUid) { - let now = Self::get_current_block_as_u64(); - Self::set_rate_limited_last_block(&RateLimitKey::OwnerHyperparamUpdate(netuid), now); + Self::deposit_event(Event::AdminFreezeWindowSet(window)); } pub fn get_owner_hyperparam_rate_limit() -> u64 { @@ -135,6 +129,7 @@ impl Pallet { pub fn set_owner_hyperparam_rate_limit(limit: u64) { OwnerHyperparamRateLimit::::set(limit); + Self::deposit_event(Event::OwnerHyperparamRateLimitSet(limit)); } // ======================== diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index eeb5b96ddb..bdf8bc5147 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -11,6 +11,7 @@ pub enum TransactionType { RegisterNetwork, SetWeightsVersionKey, SetSNOwnerHotkey, + OwnerHyperparamUpdate, } /// Implement conversion from TransactionType to u16 @@ -23,6 +24,7 @@ impl From for u16 { TransactionType::RegisterNetwork => 3, TransactionType::SetWeightsVersionKey => 4, TransactionType::SetSNOwnerHotkey => 5, + TransactionType::OwnerHyperparamUpdate => 6, } } } @@ -36,6 +38,7 @@ impl From for TransactionType { 3 => TransactionType::RegisterNetwork, 4 => TransactionType::SetWeightsVersionKey, 5 => TransactionType::SetSNOwnerHotkey, + 6 => TransactionType::OwnerHyperparamUpdate, _ => TransactionType::Unknown, } } @@ -50,6 +53,7 @@ impl Pallet { TransactionType::SetChildren => 150, // 30 minutes TransactionType::SetChildkeyTake => TxChildkeyTakeRateLimit::::get(), TransactionType::RegisterNetwork => NetworkRateLimit::::get(), + TransactionType::OwnerHyperparamUpdate => OwnerHyperparamRateLimit::::get(), TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) _ => 0, @@ -62,6 +66,7 @@ impl Pallet { TransactionType::SetWeightsVersionKey => (Tempo::::get(netuid) as u64) .saturating_mul(WeightsVersionKeyRateLimit::::get()), TransactionType::SetSNOwnerHotkey => DefaultSetSNOwnerHotkeyRateLimit::::get(), + TransactionType::OwnerHyperparamUpdate => OwnerHyperparamRateLimit::::get(), _ => Self::get_rate_limit(tx_type), } @@ -112,6 +117,9 @@ impl Pallet { TransactionType::SetSNOwnerHotkey => { Self::get_rate_limited_last_block(&RateLimitKey::SetSNOwnerHotkey(netuid)) } + TransactionType::OwnerHyperparamUpdate => { + Self::get_rate_limited_last_block(&RateLimitKey::OwnerHyperparamUpdate(netuid)) + } _ => { let tx_as_u16: u16 = (*tx_type).into(); TransactionKeyLastBlock::::get((hotkey, netuid, tx_as_u16)) @@ -139,6 +147,10 @@ impl Pallet { TransactionType::SetSNOwnerHotkey => { Self::set_rate_limited_last_block(&RateLimitKey::SetSNOwnerHotkey(netuid), block) } + TransactionType::OwnerHyperparamUpdate => Self::set_rate_limited_last_block( + &RateLimitKey::OwnerHyperparamUpdate(netuid), + block, + ), _ => { let tx_as_u16: u16 = (*tx_type).into(); TransactionKeyLastBlock::::insert((key, netuid, tx_as_u16), block); From 1c30fed681b7a88705dcae90b5c87c1cf30a5547 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Thu, 4 Sep 2025 13:06:59 +0300 Subject: [PATCH 3/8] Add tests --- pallets/admin-utils/src/tests/mock.rs | 5 +- pallets/admin-utils/src/tests/mod.rs | 130 +++++++++++++++- pallets/subtensor/src/tests/ensure.rs | 156 +++++++++++++++++++ pallets/subtensor/src/tests/mod.rs | 1 + pallets/subtensor/src/utils/rate_limiting.rs | 4 +- 5 files changed, 291 insertions(+), 5 deletions(-) create mode 100644 pallets/subtensor/src/tests/ensure.rs diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 35934bc846..0b23d9285b 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -476,7 +476,10 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .build_storage() .unwrap(); let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + System::set_block_number(1); + SubtensorModule::set_admin_freeze_window(1); + }); ext } diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 5290d3ddfc..9f6095b821 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -175,7 +175,7 @@ fn test_sudo_set_weights_version_key_rate_limit() { SubnetOwner::::insert(netuid, sn_owner); let rate_limit = WeightsVersionKeyRateLimit::::get(); - let tempo: u16 = Tempo::::get(netuid); + let tempo = Tempo::::get(netuid); let rate_limit_period = rate_limit * (tempo as u64); @@ -205,7 +205,7 @@ fn test_sudo_set_weights_version_key_rate_limit() { ); // Wait for rate limit to pass - run_to_block(rate_limit_period + 2); + run_to_block(rate_limit_period + 1); assert!(SubtensorModule::passes_rate_limit_on_subnet( &pallet_subtensor::utils::rate_limiting::TransactionType::SetWeightsVersionKey, &sn_owner, @@ -1951,3 +1951,129 @@ fn test_sudo_set_commit_reveal_version() { ); }); } + +#[test] +fn test_sudo_set_admin_freeze_window_and_rate() { + new_test_ext().execute_with(|| { + // Non-root fails + assert_eq!( + AdminUtils::sudo_set_admin_freeze_window( + <::RuntimeOrigin>::signed(U256::from(1)), + 7 + ), + Err(DispatchError::BadOrigin) + ); + // Root succeeds + assert_ok!(AdminUtils::sudo_set_admin_freeze_window( + <::RuntimeOrigin>::root(), + 7 + )); + assert_eq!(SubtensorModule::get_admin_freeze_window(), 7); + + // Owner hyperparam rate limit setter + assert_eq!( + AdminUtils::sudo_set_owner_hparam_rate_limit( + <::RuntimeOrigin>::signed(U256::from(1)), + 5 + ), + Err(DispatchError::BadOrigin) + ); + assert_ok!(AdminUtils::sudo_set_owner_hparam_rate_limit( + <::RuntimeOrigin>::root(), + 5 + )); + assert_eq!(SubtensorModule::get_owner_hyperparam_rate_limit(), 5); + }); +} + +#[test] +fn test_freeze_window_blocks_root_and_owner() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + let tempo = 10; + // Create subnet with tempo 10 + add_network(netuid, tempo); + // Set freeze window to 3 blocks + assert_ok!(AdminUtils::sudo_set_admin_freeze_window( + <::RuntimeOrigin>::root(), + 3 + )); + // Advance to a block where remaining < 3 + run_to_block((tempo - 2).into()); + + // Root should be blocked during freeze window + assert_noop!( + AdminUtils::sudo_set_min_burn( + <::RuntimeOrigin>::root(), + netuid, + 123.into() + ), + SubtensorError::::AdminActionProhibitedDuringWeightsWindow + ); + + // Owner should be blocked during freeze window as well + // Set owner + let owner: U256 = U256::from(9); + SubnetOwner::::insert(netuid, owner); + assert_noop!( + AdminUtils::sudo_set_kappa( + <::RuntimeOrigin>::signed(owner), + netuid, + 77 + ), + SubtensorError::::AdminActionProhibitedDuringWeightsWindow + ); + }); +} + +#[test] +fn test_owner_hyperparam_update_rate_limit_enforced() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + add_network(netuid, 10); + // Set owner + let owner: U256 = U256::from(5); + SubnetOwner::::insert(netuid, owner); + + // Configure owner hyperparam RL to 2 blocks + assert_ok!(AdminUtils::sudo_set_owner_hparam_rate_limit( + <::RuntimeOrigin>::root(), + 2 + )); + + // First update succeeds + assert_ok!(AdminUtils::sudo_set_kappa( + <::RuntimeOrigin>::signed(owner), + netuid, + 11 + )); + // Immediate second update fails due to TxRateLimitExceeded + assert_noop!( + AdminUtils::sudo_set_kappa( + <::RuntimeOrigin>::signed(owner), + netuid, + 12 + ), + SubtensorError::::TxRateLimitExceeded + ); + + // Advance less than limit still fails + run_to_block(SubtensorModule::get_current_block_as_u64() + 1); + assert_noop!( + AdminUtils::sudo_set_kappa( + <::RuntimeOrigin>::signed(owner), + netuid, + 13 + ), + SubtensorError::::TxRateLimitExceeded + ); + + // Advance one more block to pass the limit; should succeed + run_to_block(SubtensorModule::get_current_block_as_u64() + 1); + assert_ok!(AdminUtils::sudo_set_kappa( + <::RuntimeOrigin>::signed(owner), + netuid, + 14 + )); + }); +} diff --git a/pallets/subtensor/src/tests/ensure.rs b/pallets/subtensor/src/tests/ensure.rs new file mode 100644 index 0000000000..a8b3843fa3 --- /dev/null +++ b/pallets/subtensor/src/tests/ensure.rs @@ -0,0 +1,156 @@ +use frame_support::{assert_noop, assert_ok}; +use frame_system::Config; +use sp_core::U256; +use subtensor_runtime_common::NetUid; + +use super::mock::*; +use crate::utils::rate_limiting::TransactionType; +use crate::{RateLimitKey, SubnetOwner, SubtokenEnabled}; + +#[test] +fn ensure_subnet_owner_returns_who_and_checks_ownership() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + add_network(netuid, 10, 0); + + let owner: U256 = U256::from(42); + SubnetOwner::::insert(netuid, owner); + + // Non-owner signed should fail + assert!( + crate::Pallet::::ensure_subnet_owner( + <::RuntimeOrigin>::signed(U256::from(7)), + netuid + ) + .is_err() + ); + + // Owner signed returns who + let who = crate::Pallet::::ensure_subnet_owner( + <::RuntimeOrigin>::signed(owner), + netuid, + ) + .expect("owner must pass"); + assert_eq!(who, owner); + }); +} + +#[test] +fn ensure_subnet_owner_or_root_distinguishes_root_and_owner() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(2); + add_network(netuid, 10, 0); + let owner: U256 = U256::from(9); + SubnetOwner::::insert(netuid, owner); + + // Root path returns None + let root = crate::Pallet::::ensure_subnet_owner_or_root( + <::RuntimeOrigin>::root(), + netuid, + ) + .expect("root allowed"); + assert!(root.is_none()); + + // Owner path returns Some(owner) + let maybe_owner = crate::Pallet::::ensure_subnet_owner_or_root( + <::RuntimeOrigin>::signed(owner), + netuid, + ) + .expect("owner allowed"); + assert_eq!(maybe_owner, Some(owner)); + }); +} + +#[test] +fn ensure_root_with_rate_limit_blocks_in_freeze_window() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let tempo = 10; + add_network(netuid, 10, 0); + + // Set freeze window to 3 + let freeze_window = 3; + crate::Pallet::::set_admin_freeze_window(freeze_window); + + run_to_block((tempo - freeze_window + 1).into()); + + // Root is blocked in freeze window + assert!( + crate::Pallet::::ensure_root_with_rate_limit( + <::RuntimeOrigin>::root(), + netuid + ) + .is_err() + ); + }); +} + +#[test] +fn ensure_owner_or_root_with_limits_checks_rl_and_freeze() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(1); + let tempo = 10; + add_network(netuid, 10, 0); + SubtokenEnabled::::insert(netuid, true); + let owner: U256 = U256::from(5); + SubnetOwner::::insert(netuid, owner); + // Set freeze window to 3 + let freeze_window = 3; + crate::Pallet::::set_admin_freeze_window(freeze_window); + + // Set owner RL to 2 blocks + crate::Pallet::::set_owner_hyperparam_rate_limit(2); + + // Outside freeze window initially; should pass and return Some(owner) + let res = crate::Pallet::::ensure_sn_owner_or_root_with_limits( + <::RuntimeOrigin>::signed(owner), + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ) + .expect("should pass"); + assert_eq!(res, Some(owner)); + + // Simulate previous update at current block -> next call should fail due to rate limit + let now = crate::Pallet::::get_current_block_as_u64(); + crate::Pallet::::set_rate_limited_last_block( + &RateLimitKey::OwnerHyperparamUpdate(netuid), + now, + ); + assert_noop!( + crate::Pallet::::ensure_sn_owner_or_root_with_limits( + <::RuntimeOrigin>::signed(owner), + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ), + crate::Error::::TxRateLimitExceeded + ); + + // Advance beyond RL and ensure passes again + run_to_block(now + 3); + assert_ok!(crate::Pallet::::ensure_sn_owner_or_root_with_limits( + <::RuntimeOrigin>::signed(owner), + netuid, + &[TransactionType::OwnerHyperparamUpdate] + )); + + // Now advance into the freeze window; ensure blocks + // (using loop for clarity, because epoch calculation function uses netuid) + let freeze_window = freeze_window as u64; + loop { + let cur = crate::Pallet::::get_current_block_as_u64(); + let rem = crate::Pallet::::blocks_until_next_epoch(netuid, tempo, cur); + if rem < freeze_window { + break; + } + run_to_block(cur + 1); + } + assert_noop!( + crate::Pallet::::ensure_sn_owner_or_root_with_limits( + <::RuntimeOrigin>::signed(owner), + netuid, + &[TransactionType::OwnerHyperparamUpdate], + ), + crate::Error::::AdminActionProhibitedDuringWeightsWindow + ); + }); +} diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index b743d7c1ff..1f4aa71363 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -5,6 +5,7 @@ mod consensus; mod delegate_info; mod difficulty; mod emission; +mod ensure; mod epoch; mod evm; mod leasing; diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index bdf8bc5147..d0a05766da 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -66,7 +66,6 @@ impl Pallet { TransactionType::SetWeightsVersionKey => (Tempo::::get(netuid) as u64) .saturating_mul(WeightsVersionKeyRateLimit::::get()), TransactionType::SetSNOwnerHotkey => DefaultSetSNOwnerHotkeyRateLimit::::get(), - TransactionType::OwnerHyperparamUpdate => OwnerHyperparamRateLimit::::get(), _ => Self::get_rate_limit(tx_type), } @@ -135,7 +134,8 @@ impl Pallet { } } - /// Set the block number of the last transaction for a specific hotkey, network, and transaction type + /// Set the block number of the last transaction for a specific hotkey, network, and transaction + /// type pub fn set_last_transaction_block_on_subnet( key: &T::AccountId, netuid: NetUid, From d5f24047b82f5144dd0d81b42f9a84e244dfb306 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Thu, 4 Sep 2025 16:20:53 +0300 Subject: [PATCH 4/8] Reformat --- pallets/admin-utils/src/benchmarking.rs | 12 ++++++++++++ pallets/subtensor/src/utils/misc.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 61df5d55f8..5612100b90 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -346,5 +346,17 @@ mod benchmarks { _(RawOrigin::Root, 5u16/*version*/)/*sudo_set_commit_reveal_version()*/; } + #[benchmark] + fn sudo_set_admin_freeze_window() { + #[extrinsic_call] + _(RawOrigin::Root, 5u16/*window*/)/*sudo_set_admin_freeze_window*/; + } + + #[benchmark] + fn sudo_set_owner_hparam_rate_limit() { + #[extrinsic_call] + _(RawOrigin::Root, 10u64/*limit*/)/*sudo_set_owner_hparam_rate_limit*/; + } + //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 14b617a0e0..0951cd3ead 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -50,7 +50,7 @@ impl Pallet { /// Ensure owner-or-root with a set of TransactionType rate checks (owner only). /// - Root: only freeze window is enforced; no TransactionType checks. /// - Owner (Signed): freeze window plus all rate checks in `limits` using signer extracted from - /// origin. + /// origin. pub fn ensure_sn_owner_or_root_with_limits( o: T::RuntimeOrigin, netuid: NetUid, From c295eaaa2cacae5443cf2354759c4726cd1af967 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Fri, 5 Sep 2025 15:48:10 +0300 Subject: [PATCH 5/8] Update spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index cc9c02eca3..644a85ebcd 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 309, + spec_version: 310, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 8a7661c6a9db16737d8b437783acb2a535644b71 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Fri, 5 Sep 2025 15:57:22 +0300 Subject: [PATCH 6/8] Prove default hyperparam setting rate limit doesn't block --- pallets/admin-utils/src/tests/mod.rs | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 15d6f634f5..df11fd9912 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2137,6 +2137,38 @@ fn test_owner_hyperparam_update_rate_limit_enforced() { }); } +// Verifies that when the owner hyperparameter rate limit is left at its default (0), hyperparameter +// updates are not blocked until a non-zero value is set. +#[test] +fn test_hyperparam_rate_limit_not_blocking_with_default() { + new_test_ext().execute_with(|| { + // Setup subnet and owner + let netuid = NetUid::from(42); + add_network(netuid, 10); + let owner: U256 = U256::from(77); + SubnetOwner::::insert(netuid, owner); + + // Read the default (unset) owner hyperparam rate limit + let default_limit = SubtensorModule::get_owner_hyperparam_rate_limit(); + + assert_eq!(default_limit, 0); + + // First owner update should always succeed + assert_ok!(AdminUtils::sudo_set_kappa( + <::RuntimeOrigin>::signed(owner), + netuid, + 1 + )); + + // With default == 0, second immediate update should also pass (no rate limiting) + assert_ok!(AdminUtils::sudo_set_kappa( + <::RuntimeOrigin>::signed(owner), + netuid, + 2 + )); + }); +} + #[test] fn test_sudo_set_max_burn() { new_test_ext().execute_with(|| { From a9470a5e4bc76ecc147c5f721317f918728c94f7 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Tue, 9 Sep 2025 15:23:35 +0300 Subject: [PATCH 7/8] Update EVM tests and cleanup --- evm-tests/README.md | 6 ++ .../test/staking.precompile.stake-get.test.ts | 2 +- .../subnet.precompile.hyperparameter.test.ts | 17 ++++- pallets/admin-utils/src/lib.rs | 66 +++++++------------ pallets/admin-utils/src/tests/mod.rs | 6 +- pallets/subtensor/src/utils/misc.rs | 25 +++---- 6 files changed, 64 insertions(+), 58 deletions(-) diff --git a/evm-tests/README.md b/evm-tests/README.md index 83dc8f326f..ed3782e0f7 100644 --- a/evm-tests/README.md +++ b/evm-tests/README.md @@ -13,6 +13,12 @@ between runtime and precompile contracts. ## polkadot api +You need `polkadot-api` globally installed: + +```bash +$ npm i -g polkadot-api +``` + To get the metadata, you need start the localnet via run `./scripts/localnet.sh`. then run following command to get metadata, a folder name .papi will be created, which include the metadata and type definitions. diff --git a/evm-tests/test/staking.precompile.stake-get.test.ts b/evm-tests/test/staking.precompile.stake-get.test.ts index d9cc79aeab..4730e310d9 100644 --- a/evm-tests/test/staking.precompile.stake-get.test.ts +++ b/evm-tests/test/staking.precompile.stake-get.test.ts @@ -45,7 +45,7 @@ describe("Test staking precompile get methods", () => { await contract.getStake(hotkey.publicKey, coldkey.publicKey, netuid) ); - // validator returned as bigint now. + // validator returned as bigint now. const validators = await contract.getAlphaStakedValidators(hotkey.publicKey, netuid) diff --git a/evm-tests/test/subnet.precompile.hyperparameter.test.ts b/evm-tests/test/subnet.precompile.hyperparameter.test.ts index b8a6f19075..5d81049d41 100644 --- a/evm-tests/test/subnet.precompile.hyperparameter.test.ts +++ b/evm-tests/test/subnet.precompile.hyperparameter.test.ts @@ -1,6 +1,6 @@ import * as assert from "assert"; -import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { getDevnetApi, getRandomSubstrateKeypair, getAliceSigner, waitForTransactionWithRetry } from "../src/substrate" import { devnet } from "@polkadot-api/descriptors" import { TypedApi } from "polkadot-api"; import { convertPublicKeyToSs58 } from "../src/address-utils" @@ -25,6 +25,21 @@ describe("Test the Subnet precompile contract", () => { await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey1.publicKey)) await forceSetBalanceToSs58Address(api, convertPublicKeyToSs58(hotkey2.publicKey)) await forceSetBalanceToEthAddress(api, wallet.address) + + // Disable admin freeze window and owner hyperparam rate limiting for tests + { + const alice = getAliceSigner() + + // Set AdminFreezeWindow to 0 + const setFreezeWindow = api.tx.AdminUtils.sudo_set_admin_freeze_window({ window: 0 }) + const sudoFreezeTx = api.tx.Sudo.sudo({ call: setFreezeWindow.decodedCall }) + await waitForTransactionWithRetry(api, sudoFreezeTx, alice) + + // Set OwnerHyperparamRateLimit to 0 + const setOwnerRateLimit = api.tx.AdminUtils.sudo_set_owner_hparam_rate_limit({ limit: BigInt(0) }) + const sudoOwnerRateTx = api.tx.Sudo.sudo({ call: setOwnerRateLimit.decodedCall }) + await waitForTransactionWithRetry(api, sudoOwnerRateTx, alice) + } }) it("Can register network without identity info", async () => { diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 9a52742c80..5609fadd10 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -220,7 +220,7 @@ pub mod pallet { )?; pallet_subtensor::Pallet::::set_serving_rate_limit(netuid, serving_rate_limit); log::debug!("ServingRateLimitSet( serving_rate_limit: {serving_rate_limit:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -279,7 +279,7 @@ pub mod pallet { log::debug!( "MaxDifficultySet( netuid: {netuid:?} max_difficulty: {max_difficulty:?} ) " ); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -313,7 +313,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[ @@ -407,7 +407,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); pallet_subtensor::Pallet::::set_adjustment_alpha(netuid, adjustment_alpha); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -439,7 +439,7 @@ pub mod pallet { Error::::SubnetDoesNotExist ); pallet_subtensor::Pallet::::set_max_weight_limit(netuid, max_weight_limit); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -473,7 +473,7 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_immunity_period(netuid, immunity_period); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -510,7 +510,7 @@ pub mod pallet { log::debug!( "MinAllowedWeightSet( netuid: {netuid:?} min_allowed_weights: {min_allowed_weights:?} ) " ); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -566,7 +566,7 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_kappa(netuid, kappa); log::debug!("KappaSet( netuid: {netuid:?} kappa: {kappa:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -594,7 +594,7 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_rho(netuid, rho); log::debug!("RhoSet( netuid: {netuid:?} rho: {rho:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -634,7 +634,7 @@ pub mod pallet { log::debug!( "ActivityCutoffSet( netuid: {netuid:?} activity_cutoff: {activity_cutoff:?} ) " ); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -695,7 +695,7 @@ pub mod pallet { log::debug!( "NetworkPowRegistrationAllowed( registration_allowed: {registration_allowed:?} ) " ); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -763,7 +763,7 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_min_burn(netuid, min_burn); log::debug!("MinBurnSet( netuid: {netuid:?} min_burn: {min_burn:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -803,7 +803,7 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_max_burn(netuid, max_burn); log::debug!("MaxBurnSet( netuid: {netuid:?} max_burn: {max_burn:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -898,7 +898,7 @@ pub mod pallet { log::debug!( "BondsMovingAverageSet( netuid: {netuid:?} bonds_moving_average: {bonds_moving_average:?} ) " ); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -930,7 +930,7 @@ pub mod pallet { ); pallet_subtensor::Pallet::::set_bonds_penalty(netuid, bonds_penalty); log::debug!("BondsPenalty( netuid: {netuid:?} bonds_penalty: {bonds_penalty:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1215,7 +1215,7 @@ pub mod pallet { pallet_subtensor::Pallet::::set_commit_reveal_weights_enabled(netuid, enabled); log::debug!("ToggleSetWeightsCommitReveal( netuid: {netuid:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1246,7 +1246,7 @@ pub mod pallet { )?; pallet_subtensor::Pallet::::set_liquid_alpha_enabled(netuid, enabled); log::debug!("LiquidAlphaEnableToggled( netuid: {netuid:?}, Enabled: {enabled:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1272,7 +1272,7 @@ pub mod pallet { origin, netuid, alpha_low, alpha_high, ); if res.is_ok() { - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1384,7 +1384,7 @@ pub mod pallet { log::debug!("SetWeightCommitInterval( netuid: {netuid:?}, interval: {interval:?} ) "); pallet_subtensor::Pallet::::set_reveal_period(netuid, interval)?; - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1469,7 +1469,7 @@ pub mod pallet { )?; let res = pallet_subtensor::Pallet::::toggle_transfer(netuid, toggle); if res.is_ok() { - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1624,7 +1624,7 @@ pub mod pallet { pallet_subtensor::Pallet::::set_alpha_sigmoid_steepness(netuid, steepness); log::debug!("AlphaSigmoidSteepnessSet( netuid: {netuid:?}, steepness: {steepness:?} )"); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1657,7 +1657,7 @@ pub mod pallet { Self::deposit_event(Event::Yuma3EnableToggled { netuid, enabled }); log::debug!("Yuma3EnableToggled( netuid: {netuid:?}, Enabled: {enabled:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1690,7 +1690,7 @@ pub mod pallet { Self::deposit_event(Event::BondsResetToggled { netuid, enabled }); log::debug!("BondsResetToggled( netuid: {netuid:?} bonds_reset: {enabled:?} ) "); - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1800,7 +1800,7 @@ pub mod pallet { &[TransactionType::OwnerHyperparamUpdate], )?; pallet_subtensor::Pallet::::set_owner_immune_neuron_limit(netuid, immune_neurons)?; - Self::record_owner_rl( + pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, &[TransactionType::OwnerHyperparamUpdate], @@ -1847,24 +1847,6 @@ pub mod pallet { Ok(()) } } - - impl Pallet { - // Helper: if owner path, record last-blocks for the provided TransactionTypes - fn record_owner_rl( - maybe_owner: Option<::AccountId>, - netuid: NetUid, - txs: &[TransactionType], - ) { - if let Some(who) = maybe_owner { - let now = pallet_subtensor::Pallet::::get_current_block_as_u64(); - for tx in txs { - pallet_subtensor::Pallet::::set_last_transaction_block_on_subnet( - &who, netuid, tx, now, - ); - } - } - } - } } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index df11fd9912..9b0197860c 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1968,7 +1968,7 @@ fn test_sudo_set_admin_freeze_window_and_rate() { <::RuntimeOrigin>::root(), 7 )); - assert_eq!(SubtensorModule::get_admin_freeze_window(), 7); + assert_eq!(pallet_subtensor::AdminFreezeWindow::::get(), 7); // Owner hyperparam rate limit setter assert_eq!( @@ -1982,7 +1982,7 @@ fn test_sudo_set_admin_freeze_window_and_rate() { <::RuntimeOrigin>::root(), 5 )); - assert_eq!(SubtensorModule::get_owner_hyperparam_rate_limit(), 5); + assert_eq!(pallet_subtensor::OwnerHyperparamRateLimit::::get(), 5); }); } @@ -2149,7 +2149,7 @@ fn test_hyperparam_rate_limit_not_blocking_with_default() { SubnetOwner::::insert(netuid, owner); // Read the default (unset) owner hyperparam rate limit - let default_limit = SubtensorModule::get_owner_hyperparam_rate_limit(); + let default_limit = pallet_subtensor::OwnerHyperparamRateLimit::::get(); assert_eq!(default_limit, 0); diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 0951cd3ead..cc4485bf55 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -111,27 +111,30 @@ impl Pallet { Ok(()) } - // (Removed dedicated ensure_owner_hparam_rate_limit; OwnerHyperparamUpdate is checked via TransactionType) - - // === Admin freeze window accessors === - pub fn get_admin_freeze_window() -> u16 { - AdminFreezeWindow::::get() - } - pub fn set_admin_freeze_window(window: u16) { AdminFreezeWindow::::set(window); Self::deposit_event(Event::AdminFreezeWindowSet(window)); } - pub fn get_owner_hyperparam_rate_limit() -> u64 { - OwnerHyperparamRateLimit::::get() - } - pub fn set_owner_hyperparam_rate_limit(limit: u64) { OwnerHyperparamRateLimit::::set(limit); Self::deposit_event(Event::OwnerHyperparamRateLimitSet(limit)); } + /// If owner is `Some`, record last-blocks for the provided `TransactionType`s. + pub fn record_owner_rl( + maybe_owner: Option<::AccountId>, + netuid: NetUid, + txs: &[TransactionType], + ) { + if let Some(who) = maybe_owner { + let now = Self::get_current_block_as_u64(); + for tx in txs { + Self::set_last_transaction_block_on_subnet(&who, netuid, tx, now); + } + } + } + // ======================== // ==== Global Setters ==== // ======================== From 8089518a62a022dfcbeb5a3b2d075f9de510fc4f Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Tue, 9 Sep 2025 18:23:19 +0300 Subject: [PATCH 8/8] Update EVM tests --- .../neuron.precompile.reveal-weights.test.ts | 18 ++++++++++++++++-- .../test/neuron.precompile.set-weights.test.ts | 18 ++++++++++++++++-- .../test/staking.precompile.reward.test.ts | 16 +++++++++++++++- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/evm-tests/test/neuron.precompile.reveal-weights.test.ts b/evm-tests/test/neuron.precompile.reveal-weights.test.ts index 4ac63468db..52ddc91967 100644 --- a/evm-tests/test/neuron.precompile.reveal-weights.test.ts +++ b/evm-tests/test/neuron.precompile.reveal-weights.test.ts @@ -1,5 +1,5 @@ import * as assert from "assert"; -import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate" import { devnet } from "@polkadot-api/descriptors" import { PolkadotSigner, TypedApi } from "polkadot-api"; import { convertPublicKeyToSs58, convertH160ToSS58 } from "../src/address-utils" @@ -70,6 +70,20 @@ describe("Test neuron precompile reveal weights", () => { await startCall(api, netuid, coldkey) console.log("test the case on subnet ", netuid) + // Disable admin freeze window and owner hyperparam rate limiting for tests + { + const alice = getAliceSigner() + + // Set AdminFreezeWindow to 0 + const setFreezeWindow = api.tx.AdminUtils.sudo_set_admin_freeze_window({ window: 0 }) + const sudoFreezeTx = api.tx.Sudo.sudo({ call: setFreezeWindow.decodedCall }) + await waitForTransactionWithRetry(api, sudoFreezeTx, alice) + + // Set OwnerHyperparamRateLimit to 0 + const setOwnerRateLimit = api.tx.AdminUtils.sudo_set_owner_hparam_rate_limit({ limit: BigInt(0) }) + const sudoOwnerRateTx = api.tx.Sudo.sudo({ call: setOwnerRateLimit.decodedCall }) + await waitForTransactionWithRetry(api, sudoOwnerRateTx, alice) + } await setWeightsSetRateLimit(api, netuid, BigInt(0)) @@ -164,4 +178,4 @@ describe("Test neuron precompile reveal weights", () => { assert.ok(weight[1] !== undefined) } }) -}); \ No newline at end of file +}); diff --git a/evm-tests/test/neuron.precompile.set-weights.test.ts b/evm-tests/test/neuron.precompile.set-weights.test.ts index 1c9f62e773..4ecc0b36db 100644 --- a/evm-tests/test/neuron.precompile.set-weights.test.ts +++ b/evm-tests/test/neuron.precompile.set-weights.test.ts @@ -1,6 +1,6 @@ import * as assert from "assert"; -import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate" import { devnet } from "@polkadot-api/descriptors" import { TypedApi } from "polkadot-api"; import { convertH160ToSS58, convertPublicKeyToSs58, } from "../src/address-utils" @@ -38,6 +38,20 @@ describe("Test neuron precompile contract, set weights function", () => { await burnedRegister(api, netuid, convertH160ToSS58(wallet.address), coldkey) const uid = await api.query.SubtensorModule.Uids.getValue(netuid, convertH160ToSS58(wallet.address)) assert.notEqual(uid, undefined) + // Disable admin freeze window and owner hyperparam rate limiting for tests + { + const alice = getAliceSigner() + + // Set AdminFreezeWindow to 0 + const setFreezeWindow = api.tx.AdminUtils.sudo_set_admin_freeze_window({ window: 0 }) + const sudoFreezeTx = api.tx.Sudo.sudo({ call: setFreezeWindow.decodedCall }) + await waitForTransactionWithRetry(api, sudoFreezeTx, alice) + + // Set OwnerHyperparamRateLimit to 0 + const setOwnerRateLimit = api.tx.AdminUtils.sudo_set_owner_hparam_rate_limit({ limit: BigInt(0) }) + const sudoOwnerRateTx = api.tx.Sudo.sudo({ call: setOwnerRateLimit.decodedCall }) + await waitForTransactionWithRetry(api, sudoOwnerRateTx, alice) + } // disable reveal and enable direct set weights await setCommitRevealWeightsEnabled(api, netuid, false) await setWeightsSetRateLimit(api, netuid, BigInt(0)) @@ -68,4 +82,4 @@ describe("Test neuron precompile contract, set weights function", () => { }); } }) -}); \ No newline at end of file +}); diff --git a/evm-tests/test/staking.precompile.reward.test.ts b/evm-tests/test/staking.precompile.reward.test.ts index 79ad977515..108e0ed88c 100644 --- a/evm-tests/test/staking.precompile.reward.test.ts +++ b/evm-tests/test/staking.precompile.reward.test.ts @@ -1,5 +1,5 @@ import * as assert from "assert"; -import { getDevnetApi, getRandomSubstrateKeypair } from "../src/substrate" +import { getAliceSigner, getDevnetApi, getRandomSubstrateKeypair, waitForTransactionWithRetry } from "../src/substrate" import { devnet } from "@polkadot-api/descriptors" import { TypedApi } from "polkadot-api"; import { convertPublicKeyToSs58 } from "../src/address-utils" @@ -39,6 +39,20 @@ describe("Test neuron precompile reward", () => { await startCall(api, netuid, coldkey) console.log("test the case on subnet ", netuid) + // Disable admin freeze window and owner hyperparam rate limiting for tests + { + const alice = getAliceSigner() + + // Set AdminFreezeWindow to 0 + const setFreezeWindow = api.tx.AdminUtils.sudo_set_admin_freeze_window({ window: 0 }) + const sudoFreezeTx = api.tx.Sudo.sudo({ call: setFreezeWindow.decodedCall }) + await waitForTransactionWithRetry(api, sudoFreezeTx, alice) + + // Set OwnerHyperparamRateLimit to 0 + const setOwnerRateLimit = api.tx.AdminUtils.sudo_set_owner_hparam_rate_limit({ limit: BigInt(0) }) + const sudoOwnerRateTx = api.tx.Sudo.sudo({ call: setOwnerRateLimit.decodedCall }) + await waitForTransactionWithRetry(api, sudoOwnerRateTx, alice) + } await setTxRateLimit(api, BigInt(0)) await setTempo(api, root_netuid, root_tempo)