From c42de52ca00ac396a503ca68f9a7d3ef4f7b7fb4 Mon Sep 17 00:00:00 2001 From: unconst Date: Fri, 29 Aug 2025 13:23:30 -0500 Subject: [PATCH 01/39] commit Cargo.lock --- pallets/admin-utils/src/lib.rs | 38 ++++- pallets/subtensor/src/subnets/uids.rs | 143 +++++++++++++++++-- pallets/subtensor/src/utils/rate_limiting.rs | 7 +- 3 files changed, 168 insertions(+), 20 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 0b1498fd44..74357ea675 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -677,12 +677,12 @@ pub mod pallet { ensure!( min_burn < TaoCurrency::from(1_000_000_000), Error::::ValueNotInBounds - ) + ); // Min burn must be less than max burn ensure!( - min_burn > pallet_subtensor::Pallet::::MaxBurn(netuid), + min_burn > pallet_subtensor::Pallet::::get_max_burn(netuid), Error::::ValueNotInBounds - ) + ); pallet_subtensor::Pallet::::set_min_burn(netuid, min_burn); log::debug!("MinBurnSet( netuid: {netuid:?} min_burn: {min_burn:?} ) "); Ok(()) @@ -709,12 +709,12 @@ pub mod pallet { ensure!( max_burn > TaoCurrency::from(100_000_000), Error::::ValueNotInBounds - ) + ); // Max burn must be greater than min burn ensure!( - max_burn > pallet_subtensor::Pallet::::MinBurn(netuid), + max_burn > pallet_subtensor::Pallet::::get_min_burn(netuid), Error::::ValueNotInBounds - ) + ); pallet_subtensor::Pallet::::set_max_burn(netuid, max_burn); log::debug!("MaxBurnSet( netuid: {netuid:?} max_burn: {max_burn:?} ) "); Ok(()) @@ -1703,6 +1703,32 @@ pub mod pallet { pallet_subtensor::Pallet::::set_owner_immune_neuron_limit(netuid, immune_neurons)?; Ok(()) } + + /// Sets the number of immune owner neurons + #[pallet::call_index(74)] + #[pallet::weight(Weight::from_parts(15_000_000, 0) + .saturating_add(::DbWeight::get().reads(1_u64)) + .saturating_add(::DbWeight::get().writes(1_u64)))] + pub fn sudo_trim_to_max_allowed_uids( + origin: OriginFor, + netuid: NetUid, + max_n: u16, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + if let Ok(RawOrigin::Signed(who)) = origin.into() { + ensure!( + pallet_subtensor::Pallet::::passes_rate_limit_on_subnet( + &TransactionType::SetMaxAllowedUIDS, + &who, + netuid, + ), + pallet_subtensor::Error::::TxRateLimitExceeded + ); + } + pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; + Ok(()) + } + } } diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index f5a14c490b..e31701c9cc 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -16,17 +16,6 @@ impl Pallet { } } - /// Resets the trust, emission, consensus, incentive, dividends of the neuron to default - pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { - let neuron_index: usize = neuron_uid.into(); - Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); - Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. - } - /// Replace the neuron under this uid. pub fn replace_neuron( netuid: NetUid, @@ -107,6 +96,138 @@ impl Pallet { IsNetworkMember::::insert(new_hotkey.clone(), netuid, true); // Fill network is member. } + /// Appends the uid to the network. + pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { + let neuron_index: usize = neuron_uid.into(); + Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); + Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. + } + + pub fn trim_to_max_allowed_uids(netuid: NetUid, max_n: u16) -> DispatchResult { + + // Reasonable limits + ensure!( + Self::if_subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + ensure!( max_n > 16, Error::::InvalidValue ); + ensure!( max_n <= Self::get_max_allowed_uids( netuid ), Error::::InvalidValue ); + + // Set the value. + MaxAllowedUids::::insert(netuid, max_n); + + // Check if we need to trim. + let current_n: u16 = Self::get_subnetwork_n(netuid); + + // We need to trim, get rid of values between max_n and current_n. + if current_n > max_n { + + let ranks: Vec = Rank::::get(netuid); + let trimmed_ranks: Vec = ranks.into_iter().take(max_n as usize).collect(); + Rank::::insert(netuid, trimmed_ranks); + + let trust: Vec = Trust::::get(netuid); + let trimmed_trust: Vec = trust.into_iter().take(max_n as usize).collect(); + Trust::::insert(netuid, trimmed_trust); + + let active: Vec = Active::::get(netuid); + let trimmed_active: Vec = active.into_iter().take(max_n as usize).collect(); + Active::::insert(netuid, trimmed_active); + + let emission: Vec = Emission::::get(netuid); + let trimmed_emission: Vec = emission.into_iter().take(max_n as usize).collect(); + Emission::::insert(netuid, trimmed_emission); + + let consensus: Vec = Consensus::::get(netuid); + let trimmed_consensus: Vec = consensus.into_iter().take(max_n as usize).collect(); + Consensus::::insert(netuid, trimmed_consensus); + + let incentive: Vec = Incentive::::get(netuid); + let trimmed_incentive: Vec = incentive.into_iter().take(max_n as usize).collect(); + Incentive::::insert(netuid, trimmed_incentive); + + let dividends: Vec = Dividends::::get(netuid); + let trimmed_dividends: Vec = dividends.into_iter().take(max_n as usize).collect(); + Dividends::::insert(netuid, trimmed_dividends); + + let lastupdate: Vec = LastUpdate::::get(netuid); + let trimmed_lastupdate: Vec = lastupdate.into_iter().take(max_n as usize).collect(); + LastUpdate::::insert(netuid, trimmed_lastupdate); + + let pruning_scores: Vec = PruningScores::::get(netuid); + let trimmed_pruning_scores: Vec = pruning_scores.into_iter().take(max_n as usize).collect(); + PruningScores::::insert(netuid, trimmed_pruning_scores); + + let vtrust: Vec = ValidatorTrust::::get(netuid); + let trimmed_vtrust: Vec = vtrust.into_iter().take(max_n as usize).collect(); + ValidatorTrust::::insert(netuid, trimmed_vtrust); + + let vpermit: Vec = ValidatorPermit::::get(netuid); + let trimmed_vpermit: Vec = vpermit.into_iter().take(max_n as usize).collect(); + ValidatorPermit::::insert(netuid, trimmed_vpermit); + + let stake_weight: Vec = StakeWeight::::get(netuid); + let trimmed_stake_weight: Vec = stake_weight.into_iter().take(max_n as usize).collect(); + StakeWeight::::insert(netuid, trimmed_stake_weight); + + // Trim UIDs and Keys by removing entries with UID >= max_n (since UIDs are 0-indexed) + // UIDs range from 0 to current_n-1, so we remove UIDs from max_n to current_n-1 + for uid in max_n..current_n { + if let Some(hotkey) = Keys::::try_get(netuid, uid).ok() { + Uids::::remove(netuid, &hotkey); + // Remove IsNetworkMember association for the hotkey + IsNetworkMember::::remove(&hotkey, netuid); + // Remove last hotkey emission for the hotkey + LastHotkeyEmissionOnNetuid::::remove(&hotkey, netuid); + // Remove alpha dividends for the hotkey + AlphaDividendsPerSubnet::::remove(netuid, &hotkey); + // Remove tao dividends for the hotkey + TaoDividendsPerSubnet::::remove(netuid, &hotkey); + } + Keys::::remove(netuid, uid); + // Remove block at registration for the uid + BlockAtRegistration::::remove(netuid, uid); + } + + // Trim weights and bonds for removed UIDs + for uid in max_n..current_n { + Weights::::remove(netuid, uid); + Bonds::::remove(netuid, uid); + } + + // Trim axons, certificates, and prometheus info for removed hotkeys + for uid in max_n..current_n { + if let Some(hotkey) = Keys::::try_get(netuid, uid).ok() { + Axons::::remove(netuid, &hotkey); + NeuronCertificates::::remove(netuid, &hotkey); + Prometheus::::remove(netuid, &hotkey); + } + } + + // Trim weight and bond connections to removed UIDs for remaining neurons + // UIDs 0 to max_n-1 are kept, so we iterate through these valid UIDs + for uid in 0..max_n { + Weights::::mutate(netuid, uid, |weights| { + weights.retain(|(target_uid, _)| *target_uid < max_n); + }); + Bonds::::mutate(netuid, uid, |bonds| { + bonds.retain(|(target_uid, _)| *target_uid < max_n); + }); + } + + // Update the subnetwork size + SubnetworkN::::insert(netuid, max_n); + } + + // --- Ok and done. + Ok(()) + } + + /// Returns true if the uid is set on the network. /// pub fn is_uid_exist_on_network(netuid: NetUid, uid: u16) -> bool { diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index eeb5b96ddb..9cb2d4ffb3 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, + SetMaxAllowedUIDS, } /// Implement conversion from TransactionType to u16 @@ -23,6 +24,7 @@ impl From for u16 { TransactionType::RegisterNetwork => 3, TransactionType::SetWeightsVersionKey => 4, TransactionType::SetSNOwnerHotkey => 5, + TransactionType::SetMaxAllowedUIDS => 6, } } } @@ -36,6 +38,7 @@ impl From for TransactionType { 3 => TransactionType::RegisterNetwork, 4 => TransactionType::SetWeightsVersionKey, 5 => TransactionType::SetSNOwnerHotkey, + 6 => TransactionType::SetMaxAllowedUIDS, _ => TransactionType::Unknown, } } @@ -50,7 +53,7 @@ impl Pallet { TransactionType::SetChildren => 150, // 30 minutes TransactionType::SetChildkeyTake => TxChildkeyTakeRateLimit::::get(), TransactionType::RegisterNetwork => NetworkRateLimit::::get(), - + TransactionType::SetMaxAllowedUIDS => 7200 * 30, TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) _ => 0, } @@ -62,7 +65,6 @@ impl Pallet { TransactionType::SetWeightsVersionKey => (Tempo::::get(netuid) as u64) .saturating_mul(WeightsVersionKeyRateLimit::::get()), TransactionType::SetSNOwnerHotkey => DefaultSetSNOwnerHotkeyRateLimit::::get(), - _ => Self::get_rate_limit(tx_type), } } @@ -89,7 +91,6 @@ impl Pallet { let block: u64 = Self::get_current_block_as_u64(); let limit: u64 = Self::get_rate_limit_on_subnet(tx_type, netuid); let last_block: u64 = Self::get_last_transaction_block_on_subnet(hotkey, netuid, tx_type); - Self::check_passes_rate_limit(limit, block, last_block) } From bf7478ea122b6a500bb25b30d27a9603f583691e Mon Sep 17 00:00:00 2001 From: unconst Date: Fri, 29 Aug 2025 13:24:47 -0500 Subject: [PATCH 02/39] cargo clippy --- pallets/subtensor/src/subnets/uids.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index e31701c9cc..fe0380e583 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -177,7 +177,7 @@ impl Pallet { // Trim UIDs and Keys by removing entries with UID >= max_n (since UIDs are 0-indexed) // UIDs range from 0 to current_n-1, so we remove UIDs from max_n to current_n-1 for uid in max_n..current_n { - if let Some(hotkey) = Keys::::try_get(netuid, uid).ok() { + if let Ok(hotkey) = Keys::::try_get(netuid, uid) { Uids::::remove(netuid, &hotkey); // Remove IsNetworkMember association for the hotkey IsNetworkMember::::remove(&hotkey, netuid); @@ -201,7 +201,7 @@ impl Pallet { // Trim axons, certificates, and prometheus info for removed hotkeys for uid in max_n..current_n { - if let Some(hotkey) = Keys::::try_get(netuid, uid).ok() { + if let Ok(hotkey) = Keys::::try_get(netuid, uid) { Axons::::remove(netuid, &hotkey); NeuronCertificates::::remove(netuid, &hotkey); Prometheus::::remove(netuid, &hotkey); From 7e11d96fc75f64c9530f590f12b14833d5877d55 Mon Sep 17 00:00:00 2001 From: unconst Date: Fri, 29 Aug 2025 13:25:49 -0500 Subject: [PATCH 03/39] cargo fmt --- pallets/admin-utils/src/lib.rs | 7 +++---- pallets/subtensor/src/subnets/uids.rs | 28 +++++++++++++++------------ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 74357ea675..38654e0cae 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1710,9 +1710,9 @@ pub mod pallet { .saturating_add(::DbWeight::get().reads(1_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_trim_to_max_allowed_uids( - origin: OriginFor, - netuid: NetUid, - max_n: u16, + origin: OriginFor, + netuid: NetUid, + max_n: u16, ) -> DispatchResult { pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; if let Ok(RawOrigin::Signed(who)) = origin.into() { @@ -1728,7 +1728,6 @@ pub mod pallet { pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; Ok(()) } - } } diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index fe0380e583..69c64b3817 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -106,26 +106,27 @@ impl Pallet { Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. } - - pub fn trim_to_max_allowed_uids(netuid: NetUid, max_n: u16) -> DispatchResult { + pub fn trim_to_max_allowed_uids(netuid: NetUid, max_n: u16) -> DispatchResult { // Reasonable limits ensure!( Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); - ensure!( max_n > 16, Error::::InvalidValue ); - ensure!( max_n <= Self::get_max_allowed_uids( netuid ), Error::::InvalidValue ); + ensure!(max_n > 16, Error::::InvalidValue); + ensure!( + max_n <= Self::get_max_allowed_uids(netuid), + Error::::InvalidValue + ); // Set the value. MaxAllowedUids::::insert(netuid, max_n); // Check if we need to trim. let current_n: u16 = Self::get_subnetwork_n(netuid); - + // We need to trim, get rid of values between max_n and current_n. if current_n > max_n { - let ranks: Vec = Rank::::get(netuid); let trimmed_ranks: Vec = ranks.into_iter().take(max_n as usize).collect(); Rank::::insert(netuid, trimmed_ranks); @@ -139,7 +140,8 @@ impl Pallet { Active::::insert(netuid, trimmed_active); let emission: Vec = Emission::::get(netuid); - let trimmed_emission: Vec = emission.into_iter().take(max_n as usize).collect(); + let trimmed_emission: Vec = + emission.into_iter().take(max_n as usize).collect(); Emission::::insert(netuid, trimmed_emission); let consensus: Vec = Consensus::::get(netuid); @@ -155,11 +157,13 @@ impl Pallet { Dividends::::insert(netuid, trimmed_dividends); let lastupdate: Vec = LastUpdate::::get(netuid); - let trimmed_lastupdate: Vec = lastupdate.into_iter().take(max_n as usize).collect(); + let trimmed_lastupdate: Vec = + lastupdate.into_iter().take(max_n as usize).collect(); LastUpdate::::insert(netuid, trimmed_lastupdate); let pruning_scores: Vec = PruningScores::::get(netuid); - let trimmed_pruning_scores: Vec = pruning_scores.into_iter().take(max_n as usize).collect(); + let trimmed_pruning_scores: Vec = + pruning_scores.into_iter().take(max_n as usize).collect(); PruningScores::::insert(netuid, trimmed_pruning_scores); let vtrust: Vec = ValidatorTrust::::get(netuid); @@ -171,9 +175,10 @@ impl Pallet { ValidatorPermit::::insert(netuid, trimmed_vpermit); let stake_weight: Vec = StakeWeight::::get(netuid); - let trimmed_stake_weight: Vec = stake_weight.into_iter().take(max_n as usize).collect(); + let trimmed_stake_weight: Vec = + stake_weight.into_iter().take(max_n as usize).collect(); StakeWeight::::insert(netuid, trimmed_stake_weight); - + // Trim UIDs and Keys by removing entries with UID >= max_n (since UIDs are 0-indexed) // UIDs range from 0 to current_n-1, so we remove UIDs from max_n to current_n-1 for uid in max_n..current_n { @@ -226,7 +231,6 @@ impl Pallet { // --- Ok and done. Ok(()) } - /// Returns true if the uid is set on the network. /// From 7ae3dc862f9154d47d9414cd70507c24d7db82d3 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Mon, 1 Sep 2025 16:46:46 +0300 Subject: [PATCH 04/39] Disable Keys::remove check for trim_to_max_allowed_uids --- pallets/subtensor/src/subnets/uids.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 69c64b3817..d2ebd938bf 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -193,6 +193,7 @@ impl Pallet { // Remove tao dividends for the hotkey TaoDividendsPerSubnet::::remove(netuid, &hotkey); } + #[allow(unknown_lints)] Keys::::remove(netuid, uid); // Remove block at registration for the uid BlockAtRegistration::::remove(netuid, uid); From d00cfc1d8f0b1b28e01aa960fa6e4a6f2588c945 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Mon, 1 Sep 2025 17:08:34 +0300 Subject: [PATCH 05/39] Add benchmark for trim_to_max_uids --- pallets/admin-utils/src/benchmarking.rs | 11 +++++++++++ pallets/subtensor/src/subnets/uids.rs | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 61df5d55f8..e3397cfbfc 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -346,5 +346,16 @@ mod benchmarks { _(RawOrigin::Root, 5u16/*version*/)/*sudo_set_commit_reveal_version()*/; } + #[benchmark] + fn sudo_trim_to_max_allowed_uids() { + pallet_subtensor::Pallet::::init_new_network( + 1u16.into(), /*netuid*/ + 1u16, /*sudo_tempo*/ + ); + + #[extrinsic_call] + _(RawOrigin::Root, 1u16.into()/*netuid*/, 4097u16/*max_allowed_uids*/)/*sudo_trim_to_max_allowed_uids()*/; + } + //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index d2ebd938bf..0eb3be2ddb 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -193,7 +193,7 @@ impl Pallet { // Remove tao dividends for the hotkey TaoDividendsPerSubnet::::remove(netuid, &hotkey); } - #[allow(unknown_lints)] + #[allow(unknown_lints)] Keys::::remove(netuid, uid); // Remove block at registration for the uid BlockAtRegistration::::remove(netuid, uid); From 3c0ffb1088182bd22e67669258f796ff2a5201bb Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 2 Sep 2025 10:52:48 -0400 Subject: [PATCH 06/39] Fix wrong way ensure for min burn value, fix outdated comments --- pallets/admin-utils/src/lib.rs | 6 +++--- pallets/subtensor/src/subnets/uids.rs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 38654e0cae..a511266a7f 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -680,7 +680,7 @@ pub mod pallet { ); // Min burn must be less than max burn ensure!( - min_burn > pallet_subtensor::Pallet::::get_max_burn(netuid), + min_burn <= pallet_subtensor::Pallet::::get_max_burn(netuid), Error::::ValueNotInBounds ); pallet_subtensor::Pallet::::set_min_burn(netuid, min_burn); @@ -707,7 +707,7 @@ pub mod pallet { ); // Max burn must be greater than 0.1 TAO. ensure!( - max_burn > TaoCurrency::from(100_000_000), + max_burn >= TaoCurrency::from(100_000_000), Error::::ValueNotInBounds ); // Max burn must be greater than min burn @@ -1704,7 +1704,7 @@ pub mod pallet { Ok(()) } - /// Sets the number of immune owner neurons + /// Sets the maximum allowed UIDs for a subnet #[pallet::call_index(74)] #[pallet::weight(Weight::from_parts(15_000_000, 0) .saturating_add(::DbWeight::get().reads(1_u64)) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 69c64b3817..0a1121aced 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -96,7 +96,8 @@ impl Pallet { IsNetworkMember::::insert(new_hotkey.clone(), netuid, true); // Fill network is member. } - /// Appends the uid to the network. + /// Clears (sets to default) the neuron map values fot a neuron when it is + /// removed from the subnet pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { let neuron_index: usize = neuron_uid.into(); Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); From 86cce6f473bc63a9afcf2fa3e9fd877665e9a973 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 3 Sep 2025 11:49:22 -0300 Subject: [PATCH 07/39] add test + small refacto --- pallets/admin-utils/src/lib.rs | 29 +------ pallets/admin-utils/src/tests/mod.rs | 108 +++++++++++++++++++++++++- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/subnets/uids.rs | 24 ++---- 4 files changed, 120 insertions(+), 43 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index a511266a7f..da81f2e208 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -107,8 +107,6 @@ pub mod pallet { BondsMovingAverageMaxReached, /// Only root can set negative sigmoid steepness values NegativeSigmoidSteepness, - /// Value not in allowed bounds. - ValueNotInBounds, } /// Enum for specifying the type of precompile operation. #[derive( @@ -667,22 +665,12 @@ pub mod pallet { netuid: NetUid, min_burn: TaoCurrency, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; - // Allow set min_burn but only up to 1 TAO for spamming. + ensure_root(origin)?; + ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - // Min burn must be less than 1 TAO. - ensure!( - min_burn < TaoCurrency::from(1_000_000_000), - Error::::ValueNotInBounds - ); - // Min burn must be less than max burn - ensure!( - min_burn <= pallet_subtensor::Pallet::::get_max_burn(netuid), - Error::::ValueNotInBounds - ); pallet_subtensor::Pallet::::set_min_burn(netuid, min_burn); log::debug!("MinBurnSet( netuid: {netuid:?} min_burn: {min_burn:?} ) "); Ok(()) @@ -700,21 +688,12 @@ pub mod pallet { netuid: NetUid, max_burn: TaoCurrency, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + ensure_root(origin)?; + ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist ); - // Max burn must be greater than 0.1 TAO. - ensure!( - max_burn >= TaoCurrency::from(100_000_000), - Error::::ValueNotInBounds - ); - // Max burn must be greater than min burn - ensure!( - max_burn > pallet_subtensor::Pallet::::get_min_burn(netuid), - Error::::ValueNotInBounds - ); pallet_subtensor::Pallet::::set_max_burn(netuid, max_burn); log::debug!("MaxBurnSet( netuid: {netuid:?} max_burn: {max_burn:?} ) "); Ok(()) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 5290d3ddfc..01bd0e68f1 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -5,7 +5,10 @@ use frame_support::{ traits::Hooks, }; use frame_system::Config; -use pallet_subtensor::{Error as SubtensorError, SubnetOwner, Tempo, WeightsVersionKeyRateLimit}; +use pallet_subtensor::{ + Error as SubtensorError, MaxRegistrationsPerBlock, Rank, SubnetOwner, + TargetRegistrationsPerInterval, Tempo, WeightsVersionKeyRateLimit, *, +}; // use pallet_subtensor::{migrations, Event}; use pallet_subtensor::Event; use sp_consensus_grandpa::AuthorityId as GrandpaId; @@ -1951,3 +1954,106 @@ fn test_sudo_set_commit_reveal_version() { ); }); } + +#[test] +fn test_trim_to_max_allowed_uids() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + add_network(netuid, 10); + MaxRegistrationsPerBlock::::insert(netuid, 256); + TargetRegistrationsPerInterval::::insert(netuid, 256); + + // Add some neurons + let max_n = 32; + for i in 1..=max_n { + let n = i * 1000; + register_ok_neuron(netuid, U256::from(n), U256::from(n + i), 0); + } + + // Run some block to ensure stake weights are set + run_to_block(20); + + // Normal case + let new_max_n = 20; + assert_ok!(AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + new_max_n + )); + + // Ensure storage has been trimmed + assert_eq!(MaxAllowedUids::::get(netuid), new_max_n); + assert_eq!(Rank::::get(netuid).len(), new_max_n as usize); + assert_eq!(Trust::::get(netuid).len(), new_max_n as usize); + assert_eq!(Active::::get(netuid).len(), new_max_n as usize); + assert_eq!(Emission::::get(netuid).len(), new_max_n as usize); + assert_eq!(Consensus::::get(netuid).len(), new_max_n as usize); + assert_eq!(Incentive::::get(netuid).len(), new_max_n as usize); + assert_eq!(Dividends::::get(netuid).len(), new_max_n as usize); + assert_eq!(LastUpdate::::get(netuid).len(), new_max_n as usize); + assert_eq!(PruningScores::::get(netuid).len(), new_max_n as usize); + assert_eq!( + ValidatorTrust::::get(netuid).len(), + new_max_n as usize + ); + assert_eq!( + ValidatorPermit::::get(netuid).len(), + new_max_n as usize + ); + assert_eq!(StakeWeight::::get(netuid).len(), new_max_n as usize); + + for uid in max_n..new_max_n { + assert!(!Keys::::contains_key(netuid, uid)); + assert!(!BlockAtRegistration::::contains_key(netuid, uid)); + assert!(!Weights::::contains_key(netuid, uid)); + assert!(!Bonds::::contains_key(netuid, uid)); + } + + for uid in 0..max_n { + assert!( + Weights::::get(netuid, uid) + .iter() + .all(|(target_uid, _)| *target_uid < new_max_n), + "Found a weight with target_uid >= new_max_n" + ); + assert!( + Bonds::::get(netuid, uid) + .iter() + .all(|(target_uid, _)| *target_uid < new_max_n), + "Found a bond with target_uid >= new_max_n" + ); + } + + assert_eq!(SubnetworkN::::get(netuid), new_max_n); + + // Non existent subnet + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + NetUid::from(42), + new_max_n + ), + pallet_subtensor::Error::::SubNetworkDoesNotExist + ); + + // New max n less than lower bound + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + 15 + ), + pallet_subtensor::Error::::InvalidValue + ); + + // New max n greater than upper bound + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + SubtensorModule::get_max_allowed_uids(netuid) + 1 + ), + pallet_subtensor::Error::::InvalidValue + ); + }); +} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ef41b07c78..e6d2b658aa 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1486,7 +1486,7 @@ pub mod pallet { /// ==== Subnetwork Consensus Storage ==== /// ======================================= #[pallet::storage] // --- DMAP ( netuid ) --> stake_weight | weight for stake used in YC. - pub(super) type StakeWeight = + pub type StakeWeight = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid, hotkey ) --> uid diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index ef7ed1b6ac..cdec803598 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -96,7 +96,7 @@ impl Pallet { IsNetworkMember::::insert(new_hotkey.clone(), netuid, true); // Fill network is member. } - /// Clears (sets to default) the neuron map values fot a neuron when it is + /// Clears (sets to default) the neuron map values fot a neuron when it is /// removed from the subnet pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { let neuron_index: usize = neuron_uid.into(); @@ -180,9 +180,9 @@ impl Pallet { stake_weight.into_iter().take(max_n as usize).collect(); StakeWeight::::insert(netuid, trimmed_stake_weight); - // Trim UIDs and Keys by removing entries with UID >= max_n (since UIDs are 0-indexed) - // UIDs range from 0 to current_n-1, so we remove UIDs from max_n to current_n-1 for uid in max_n..current_n { + // Trim UIDs and Keys by removing entries with UID >= max_n (since UIDs are 0-indexed) + // UIDs range from 0 to current_n-1, so we remove UIDs from max_n to current_n-1 if let Ok(hotkey) = Keys::::try_get(netuid, uid) { Uids::::remove(netuid, &hotkey); // Remove IsNetworkMember association for the hotkey @@ -193,28 +193,20 @@ impl Pallet { AlphaDividendsPerSubnet::::remove(netuid, &hotkey); // Remove tao dividends for the hotkey TaoDividendsPerSubnet::::remove(netuid, &hotkey); + // Trim axons, certificates, and prometheus info for removed hotkeys + Axons::::remove(netuid, &hotkey); + NeuronCertificates::::remove(netuid, &hotkey); + Prometheus::::remove(netuid, &hotkey); } #[allow(unknown_lints)] Keys::::remove(netuid, uid); // Remove block at registration for the uid BlockAtRegistration::::remove(netuid, uid); - } - - // Trim weights and bonds for removed UIDs - for uid in max_n..current_n { + // Trim weights and bonds for removed UIDs Weights::::remove(netuid, uid); Bonds::::remove(netuid, uid); } - // Trim axons, certificates, and prometheus info for removed hotkeys - for uid in max_n..current_n { - if let Ok(hotkey) = Keys::::try_get(netuid, uid) { - Axons::::remove(netuid, &hotkey); - NeuronCertificates::::remove(netuid, &hotkey); - Prometheus::::remove(netuid, &hotkey); - } - } - // Trim weight and bond connections to removed UIDs for remaining neurons // UIDs 0 to max_n-1 are kept, so we iterate through these valid UIDs for uid in 0..max_n { From 3c8ff8e29689e0f9c7f5d5376d7ab6e225d60675 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 3 Sep 2025 12:42:22 -0300 Subject: [PATCH 08/39] cargo fmt --- pallets/admin-utils/src/tests/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 331d194b15..a131c7e0f3 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2175,4 +2175,3 @@ fn test_trim_to_max_allowed_uids() { ); }); } - From eb001e35291d48fdceb5f28ae4f5ecca6fbd0afd Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 3 Sep 2025 14:36:31 -0300 Subject: [PATCH 09/39] fix misleading comment --- pallets/subtensor/src/subnets/uids.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index e116e98ac4..cfa1770029 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -202,7 +202,7 @@ impl Pallet { Keys::::remove(netuid, uid); // Remove block at registration for the uid BlockAtRegistration::::remove(netuid, uid); - // Trim weights and bonds for removed UIDs + // Remove entire weights and bonds entries for removed UIDs Weights::::remove(netuid, uid); Bonds::::remove(netuid, uid); } From b96c60c418d298a1d0c64e32c31f82c899e0dff2 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 3 Sep 2025 14:45:12 -0300 Subject: [PATCH 10/39] fix merge issue --- pallets/admin-utils/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 64e4a54c8b..a4d8ff9061 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1648,6 +1648,7 @@ pub mod pallet { ); } pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; + Ok(()) } } } From 0b5e5b1467391798d3f1c8a7119ecf48fc74df17 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 3 Sep 2025 18:18:31 -0300 Subject: [PATCH 11/39] trim by emission and compress to the left --- pallets/admin-utils/src/lib.rs | 6 +- pallets/admin-utils/src/tests/mock.rs | 6 +- pallets/admin-utils/src/tests/mod.rs | 174 +++++++++++-- .../subtensor/src/coinbase/run_coinbase.rs | 32 ++- pallets/subtensor/src/lib.rs | 14 +- pallets/subtensor/src/macros/config.rs | 8 +- pallets/subtensor/src/subnets/uids.rs | 237 ++++++++++++------ pallets/subtensor/src/tests/mock.rs | 2 - pallets/transaction-fee/src/tests/mock.rs | 2 - runtime/src/lib.rs | 3 +- 10 files changed, 349 insertions(+), 135 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index a4d8ff9061..5a49796e30 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1626,7 +1626,11 @@ pub mod pallet { Ok(()) } - /// Sets the maximum allowed UIDs for a subnet + /// Trims the maximum number of UIDs for a subnet. + /// + /// The trimming is done by sorting the UIDs by emission descending and then trimming + /// the lowest emitters while preserving temporally and owner immune UIDs. The UIDs are + /// then compressed to the left and storage is migrated to the new compressed UIDs. #[pallet::call_index(74)] #[pallet::weight(Weight::from_parts(15_000_000, 0) .saturating_add(::DbWeight::get().reads(1_u64)) diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 67099a1906..a3931b045d 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -91,7 +91,8 @@ parameter_types! { pub const InitialTempo: u16 = 0; pub const SelfOwnership: u64 = 2; pub const InitialImmunityPeriod: u16 = 2; - pub const InitialMaxAllowedUids: u16 = 2; + pub const InitialMinAllowedUids: u16 = 2; + pub const InitialMaxAllowedUids: u16 = 4; pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialBondsPenalty: u16 = u16::MAX; pub const InitialBondsResetOn: bool = false; @@ -129,7 +130,6 @@ parameter_types! { pub const InitialRAORecycledForRegistration: u64 = 0; pub const InitialSenateRequiredStakePercentage: u64 = 2; // 2 percent of total stake pub const InitialNetworkImmunityPeriod: u64 = 7200 * 7; - pub const InitialNetworkMinAllowedUids: u16 = 128; pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. @@ -174,6 +174,7 @@ impl pallet_subtensor::Config for Test { type InitialRho = InitialRho; type InitialAlphaSigmoidSteepness = InitialAlphaSigmoidSteepness; type InitialKappa = InitialKappa; + type InitialMinAllowedUids = InitialMinAllowedUids; type InitialMaxAllowedUids = InitialMaxAllowedUids; type InitialValidatorPruneLen = InitialValidatorPruneLen; type InitialScalingLawPower = InitialScalingLawPower; @@ -205,7 +206,6 @@ impl pallet_subtensor::Config for Test { type InitialRAORecycledForRegistration = InitialRAORecycledForRegistration; type InitialSenateRequiredStakePercentage = InitialSenateRequiredStakePercentage; type InitialNetworkImmunityPeriod = InitialNetworkImmunityPeriod; - type InitialNetworkMinAllowedUids = InitialNetworkMinAllowedUids; type InitialNetworkMinLockCost = InitialNetworkMinLockCost; type InitialSubnetOwnerCut = InitialSubnetOwnerCut; type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index a131c7e0f3..ccce3a1e49 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2077,57 +2077,178 @@ fn test_sudo_set_max_burn() { fn test_trim_to_max_allowed_uids() { new_test_ext().execute_with(|| { let netuid = NetUid::from(1); + let sn_owner = U256::from(1); + let sn_owner_hotkey1 = U256::from(2); + let sn_owner_hotkey2 = U256::from(3); add_network(netuid, 10); + SubnetOwner::::insert(netuid, sn_owner); + SubnetOwnerHotkey::::insert(netuid, sn_owner_hotkey1); MaxRegistrationsPerBlock::::insert(netuid, 256); TargetRegistrationsPerInterval::::insert(netuid, 256); + ImmuneOwnerUidsLimit::::insert(netuid, 2); + // We set a low value here to make testing easier + MinAllowedUids::::set(netuid, 4); // Add some neurons - let max_n = 32; + let max_n = 16; for i in 1..=max_n { let n = i * 1000; register_ok_neuron(netuid, U256::from(n), U256::from(n + i), 0); } - // Run some block to ensure stake weights are set - run_to_block(20); + // Run some block to ensure stake weights are set and that we are past the immunity period + // for all neurons + run_to_block((ImmunityPeriod::::get(netuid) + 1).into()); + + // Set some randomized values that we can keep track of + let values = vec![ + 17u16, 42u16, 8u16, 56u16, 23u16, 91u16, 34u16, // owner owned + 77u16, // temporally immune + 12u16, 65u16, 3u16, 88u16, // owner owned + 29u16, 51u16, 74u16, // temporally immune + 39u16, + ]; + let bool_values = vec![ + false, false, false, true, false, true, true, // owner owned + true, // temporally immune + false, true, false, true, // owner owned + false, true, true, // temporally immune + false, + ]; + let alpha_values = values.iter().map(|&v| (v as u64).into()).collect(); + let u64_values: Vec = values.iter().map(|&v| v as u64).collect(); + + Emission::::set(netuid, alpha_values); + Rank::::insert(netuid, values.clone()); + Trust::::insert(netuid, values.clone()); + Consensus::::insert(netuid, values.clone()); + Incentive::::insert(netuid, values.clone()); + Dividends::::insert(netuid, values.clone()); + LastUpdate::::insert(netuid, u64_values); + PruningScores::::insert(netuid, values.clone()); + ValidatorTrust::::insert(netuid, values.clone()); + StakeWeight::::insert(netuid, values); + ValidatorPermit::::insert(netuid, bool_values.clone()); + Active::::insert(netuid, bool_values); + + // We set some owner immune uids + let now = frame_system::Pallet::::block_number(); + BlockAtRegistration::::set(netuid, 6, now); + BlockAtRegistration::::set(netuid, 11, now); + + // And some temporally immune uids + Keys::::insert(netuid, 7, sn_owner_hotkey1); + Uids::::insert(netuid, sn_owner_hotkey1, 7); + Keys::::insert(netuid, 14, sn_owner_hotkey2); + Uids::::insert(netuid, sn_owner_hotkey2, 14); + + // Populate Weights and Bonds storage items to test trimming + // Create weights and bonds that span across the range that will be trimmed + for uid in 0..max_n { + let mut weights = Vec::new(); + let mut bonds = Vec::new(); + + // Add connections to all other uids, including those that will be trimmed + for target_uid in 0..max_n { + if target_uid != uid { + // Use some non-zero values to make the test more meaningful + let weight_value = ((uid + target_uid) % 1000) as u16; + let bond_value = ((uid * target_uid) % 1000) as u16; + weights.push((target_uid, weight_value)); + bonds.push((target_uid, bond_value)); + } + } + + Weights::::insert(netuid, uid, weights); + Bonds::::insert(netuid, uid, bonds); + } // Normal case - let new_max_n = 20; + let new_max_n = 8; assert_ok!(AdminUtils::sudo_trim_to_max_allowed_uids( <::RuntimeOrigin>::root(), netuid, new_max_n )); - // Ensure storage has been trimmed + // Ensure the max allowed uids has been set correctly assert_eq!(MaxAllowedUids::::get(netuid), new_max_n); - assert_eq!(Rank::::get(netuid).len(), new_max_n as usize); - assert_eq!(Trust::::get(netuid).len(), new_max_n as usize); - assert_eq!(Active::::get(netuid).len(), new_max_n as usize); - assert_eq!(Emission::::get(netuid).len(), new_max_n as usize); - assert_eq!(Consensus::::get(netuid).len(), new_max_n as usize); - assert_eq!(Incentive::::get(netuid).len(), new_max_n as usize); - assert_eq!(Dividends::::get(netuid).len(), new_max_n as usize); - assert_eq!(LastUpdate::::get(netuid).len(), new_max_n as usize); - assert_eq!(PruningScores::::get(netuid).len(), new_max_n as usize); - assert_eq!( - ValidatorTrust::::get(netuid).len(), - new_max_n as usize - ); - assert_eq!( - ValidatorPermit::::get(netuid).len(), - new_max_n as usize - ); - assert_eq!(StakeWeight::::get(netuid).len(), new_max_n as usize); - for uid in max_n..new_max_n { + // Ensure the emission has been trimmed correctly, keeping the highest emitters + // and immune and compressed to the left + assert_eq!( + Emission::::get(netuid), + vec![ + 56.into(), + 91.into(), + 34.into(), + 77.into(), + 65.into(), + 88.into(), + 51.into(), + 74.into() + ] + ); + // Ensure rest of storage has been trimmed correctly + let expected_values = vec![56, 91, 34, 77, 65, 88, 51, 74]; + let expected_bools = vec![true, true, true, true, true, true, true, true]; + let expected_u64_values = vec![56, 91, 34, 77, 65, 88, 51, 74]; + assert_eq!(Rank::::get(netuid), expected_values); + assert_eq!(Trust::::get(netuid), expected_values); + assert_eq!(Active::::get(netuid), expected_bools); + assert_eq!(Consensus::::get(netuid), expected_values); + assert_eq!(Incentive::::get(netuid), expected_values); + assert_eq!(Dividends::::get(netuid), expected_values); + assert_eq!(LastUpdate::::get(netuid), expected_u64_values); + assert_eq!(PruningScores::::get(netuid), expected_values); + assert_eq!(ValidatorTrust::::get(netuid), expected_values); + assert_eq!(ValidatorPermit::::get(netuid), expected_bools); + assert_eq!(StakeWeight::::get(netuid), expected_values); + + // Ensure trimmed uids related storage has been cleared + for uid in new_max_n..max_n { assert!(!Keys::::contains_key(netuid, uid)); assert!(!BlockAtRegistration::::contains_key(netuid, uid)); assert!(!Weights::::contains_key(netuid, uid)); assert!(!Bonds::::contains_key(netuid, uid)); } - for uid in 0..max_n { + // Ensure trimmed uids hotkey related storage has been cleared + let trimmed_hotkeys = vec![ + U256::from(1000), + U256::from(2000), + U256::from(3000), + U256::from(5000), + U256::from(9000), + U256::from(11000), + U256::from(13000), + U256::from(16000), + ]; + for hotkey in trimmed_hotkeys { + assert!(!Uids::::contains_key(netuid, &hotkey)); + assert!(!IsNetworkMember::::contains_key(&hotkey, netuid)); + assert!(!LastHotkeyEmissionOnNetuid::::contains_key( + &hotkey, netuid + )); + assert!(!AlphaDividendsPerSubnet::::contains_key( + netuid, &hotkey + )); + assert!(!TaoDividendsPerSubnet::::contains_key( + netuid, &hotkey + )); + assert!(!Axons::::contains_key(netuid, &hotkey)); + assert!(!NeuronCertificates::::contains_key(netuid, &hotkey)); + assert!(!Prometheus::::contains_key(netuid, &hotkey)); + } + + // Ensure trimmed uids weights and bonds have been cleared + for uid in new_max_n..max_n { + assert!(!Weights::::contains_key(netuid, uid)); + assert!(!Bonds::::contains_key(netuid, uid)); + } + + // Ensure trimmed uids weights and bonds connections have been trimmed correctly + for uid in 0..new_max_n { assert!( Weights::::get(netuid, uid) .iter() @@ -2142,6 +2263,7 @@ fn test_trim_to_max_allowed_uids() { ); } + // Actual number of neurons on the network updated after trimming assert_eq!(SubnetworkN::::get(netuid), new_max_n); // Non existent subnet @@ -2159,7 +2281,7 @@ fn test_trim_to_max_allowed_uids() { AdminUtils::sudo_trim_to_max_allowed_uids( <::RuntimeOrigin>::root(), netuid, - 15 + 2 ), pallet_subtensor::Error::::InvalidValue ); diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 21fc9bd603..deb2921575 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -429,6 +429,20 @@ impl Pallet { } fn get_immune_owner_hotkeys(netuid: NetUid, coldkey: &T::AccountId) -> Vec { + Self::get_immune_owner_tuples(netuid, coldkey) + .into_iter() + .map(|(_, hk)| hk) + .collect() + } + + pub fn get_immune_owner_uids(netuid: NetUid, coldkey: &T::AccountId) -> Vec { + Self::get_immune_owner_tuples(netuid, coldkey) + .into_iter() + .map(|(uid, _)| uid) + .collect() + } + + fn get_immune_owner_tuples(netuid: NetUid, coldkey: &T::AccountId) -> Vec<(u16, T::AccountId)> { // Gather (block, uid, hotkey) only for hotkeys that have a UID and a registration block. let mut triples: Vec<(u64, u16, T::AccountId)> = OwnedHotkeys::::get(coldkey) .into_iter() @@ -451,22 +465,24 @@ impl Pallet { triples.truncate(limit); } - // Project to just hotkeys - let mut immune_hotkeys: Vec = - triples.into_iter().map(|(_, _, hk)| hk).collect(); + // Project to uid/hotkey tuple + let mut immune_tuples: Vec<(u16, T::AccountId)> = + triples.into_iter().map(|(_, uid, hk)| (uid, hk)).collect(); // Insert subnet owner hotkey in the beginning of the list if valid and not // already present if let Ok(owner_hk) = SubnetOwnerHotkey::::try_get(netuid) { - if Uids::::get(netuid, &owner_hk).is_some() && !immune_hotkeys.contains(&owner_hk) { - immune_hotkeys.insert(0, owner_hk); - if immune_hotkeys.len() > limit { - immune_hotkeys.truncate(limit); + if let Some(owner_uid) = Uids::::get(netuid, &owner_hk) { + if !immune_tuples.contains(&(owner_uid, owner_hk.clone())) { + immune_tuples.insert(0, (owner_uid, owner_hk.clone())); + if immune_tuples.len() > limit { + immune_tuples.truncate(limit); + } } } } - immune_hotkeys + immune_tuples } pub fn distribute_dividends_and_incentives( diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c175261ba9..44bee03f86 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -530,11 +530,6 @@ pub mod pallet { 0 } #[pallet::type_value] - /// Default value for network min allowed UIDs. - pub fn DefaultNetworkMinAllowedUids() -> u16 { - T::InitialNetworkMinAllowedUids::get() - } - #[pallet::type_value] /// Default value for network min lock cost. pub fn DefaultNetworkMinLockCost() -> TaoCurrency { T::InitialNetworkMinLockCost::get().into() @@ -620,6 +615,11 @@ pub mod pallet { T::InitialKappa::get() } #[pallet::type_value] + /// Default value for network min allowed UIDs. + pub fn DefaultMinAllowedUids() -> u16 { + T::InitialMinAllowedUids::get() + } + #[pallet::type_value] /// Default maximum allowed UIDs. pub fn DefaultMaxAllowedUids() -> u16 { T::InitialMaxAllowedUids::get() @@ -1335,6 +1335,10 @@ pub mod pallet { pub type BurnRegistrationsThisInterval = StorageMap<_, Identity, NetUid, u16, ValueQuery>; #[pallet::storage] + /// --- MAP ( netuid ) --> min_allowed_uids + pub type MinAllowedUids = + StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMinAllowedUids>; + #[pallet::storage] /// --- MAP ( netuid ) --> max_allowed_uids pub type MaxAllowedUids = StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMaxAllowedUids>; diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index b89fe8e865..5ecc77364c 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -128,7 +128,10 @@ mod config { /// Kappa constant. #[pallet::constant] type InitialKappa: Get; - /// Max UID constant. + /// Initial minimum allowed network UIDs + #[pallet::constant] + type InitialMinAllowedUids: Get; + /// Initial maximum allowed network UIDs #[pallet::constant] type InitialMaxAllowedUids: Get; /// Initial validator context pruning length. @@ -191,9 +194,6 @@ mod config { /// Initial network immunity period #[pallet::constant] type InitialNetworkImmunityPeriod: Get; - /// Initial minimum allowed network UIDs - #[pallet::constant] - type InitialNetworkMinAllowedUids: Get; /// Initial network minimum burn cost #[pallet::constant] type InitialNetworkMinLockCost: Get; diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index cfa1770029..3d33f68bf3 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -1,5 +1,6 @@ use super::*; use frame_support::storage::IterableStorageDoubleMap; +use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use sp_std::vec; use subtensor_runtime_common::NetUid; @@ -114,115 +115,187 @@ impl Pallet { Self::if_subnet_exist(netuid), Error::::SubNetworkDoesNotExist ); - ensure!(max_n > 16, Error::::InvalidValue); ensure!( - max_n <= Self::get_max_allowed_uids(netuid), + max_n >= MinAllowedUids::::get(netuid), + Error::::InvalidValue + ); + ensure!( + max_n <= MaxAllowedUids::::get(netuid), Error::::InvalidValue ); - // Set the value. MaxAllowedUids::::insert(netuid, max_n); - // Check if we need to trim. - let current_n: u16 = Self::get_subnetwork_n(netuid); + let owner = SubnetOwner::::get(netuid); + let owner_uids = BTreeSet::from_iter(Self::get_immune_owner_uids(netuid, &owner)); + let current_n = Self::get_subnetwork_n(netuid); - // We need to trim, get rid of values between max_n and current_n. if current_n > max_n { - let ranks: Vec = Rank::::get(netuid); - let trimmed_ranks: Vec = ranks.into_iter().take(max_n as usize).collect(); - Rank::::insert(netuid, trimmed_ranks); - - let trust: Vec = Trust::::get(netuid); - let trimmed_trust: Vec = trust.into_iter().take(max_n as usize).collect(); - Trust::::insert(netuid, trimmed_trust); + // Get all emissions with their UIDs and sort by emission (descending) + // This ensures we keep the highest emitters and remove the lowest ones + let mut emissions = Emission::::get(netuid) + .into_iter() + .enumerate() + .collect::>(); + emissions.sort_by_key(|(_, emission)| std::cmp::Reverse(*emission)); + + // Remove uids from the end (lowest emitters) until we reach the new maximum + let mut removed_uids = BTreeSet::new(); + let mut uids_left_to_process = current_n; + + // Iterate from the end (lowest emitters) to the beginning + for i in (0..current_n).rev() { + if uids_left_to_process == max_n { + break; // We've reached the target number of UIDs + } - let active: Vec = Active::::get(netuid); - let trimmed_active: Vec = active.into_iter().take(max_n as usize).collect(); - Active::::insert(netuid, trimmed_active); + if let Some((uid, _)) = emissions.get(i as usize).cloned() { + // Skip subnet owner's or temporally immune uids + if owner_uids.contains(&(uid as u16)) + || Self::get_neuron_is_immune(netuid, uid as u16) + { + continue; + } + + // Remove hotkey related storage items if hotkey exists + if let Ok(hotkey) = Keys::::try_get(netuid, uid as u16) { + Uids::::remove(netuid, &hotkey); + IsNetworkMember::::remove(&hotkey, netuid); + LastHotkeyEmissionOnNetuid::::remove(&hotkey, netuid); + AlphaDividendsPerSubnet::::remove(netuid, &hotkey); + TaoDividendsPerSubnet::::remove(netuid, &hotkey); + Axons::::remove(netuid, &hotkey); + NeuronCertificates::::remove(netuid, &hotkey); + Prometheus::::remove(netuid, &hotkey); + } + + // Remove all storage items associated with this uid + Keys::::remove(netuid, uid as u16); + BlockAtRegistration::::remove(netuid, uid as u16); + Weights::::remove(netuid, uid as u16); + Bonds::::remove(netuid, uid as u16); + + // Remove from emissions array and track as removed + emissions.remove(i.into()); + removed_uids.insert(uid); + uids_left_to_process = uids_left_to_process.saturating_sub(1); + } + } - let emission: Vec = Emission::::get(netuid); - let trimmed_emission: Vec = - emission.into_iter().take(max_n as usize).collect(); - Emission::::insert(netuid, trimmed_emission); + // Sort remaining emissions by uid to compress uids to the left + // This ensures consecutive uid indices in the final arrays + emissions.sort_by_key(|(uid, _)| *uid); + + // Extract the final uids and emissions after trimming and sorting + let (trimmed_uids, trimmed_emissions): (Vec, Vec) = + emissions.into_iter().unzip(); + + // Get all current arrays from storage + let ranks = Rank::::get(netuid); + let trust = Trust::::get(netuid); + let active = Active::::get(netuid); + let consensus = Consensus::::get(netuid); + let incentive = Incentive::::get(netuid); + let dividends = Dividends::::get(netuid); + let lastupdate = LastUpdate::::get(netuid); + let pruning_scores = PruningScores::::get(netuid); + let vtrust = ValidatorTrust::::get(netuid); + let vpermit = ValidatorPermit::::get(netuid); + let stake_weight = StakeWeight::::get(netuid); + + // Create trimmed arrays by extracting values for kept uids only + // Pre-allocate vectors with exact capacity for efficiency + let mut trimmed_ranks = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_trust = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_active = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_consensus = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_incentive = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_dividends = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_lastupdate = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_pruning_scores = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_vtrust = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_vpermit = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_stake_weight = Vec::with_capacity(trimmed_uids.len()); + + // Single iteration to extract values for all kept uids + for &old_uid in &trimmed_uids { + trimmed_ranks.push(ranks.get(old_uid).cloned().unwrap_or_default()); + trimmed_trust.push(trust.get(old_uid).cloned().unwrap_or_default()); + trimmed_active.push(active.get(old_uid).cloned().unwrap_or_default()); + trimmed_consensus.push(consensus.get(old_uid).cloned().unwrap_or_default()); + trimmed_incentive.push(incentive.get(old_uid).cloned().unwrap_or_default()); + trimmed_dividends.push(dividends.get(old_uid).cloned().unwrap_or_default()); + trimmed_lastupdate.push(lastupdate.get(old_uid).cloned().unwrap_or_default()); + trimmed_pruning_scores + .push(pruning_scores.get(old_uid).cloned().unwrap_or_default()); + trimmed_vtrust.push(vtrust.get(old_uid).cloned().unwrap_or_default()); + trimmed_vpermit.push(vpermit.get(old_uid).cloned().unwrap_or_default()); + trimmed_stake_weight.push(stake_weight.get(old_uid).cloned().unwrap_or_default()); + } - let consensus: Vec = Consensus::::get(netuid); - let trimmed_consensus: Vec = consensus.into_iter().take(max_n as usize).collect(); + // Update storage with trimmed arrays + Emission::::insert(netuid, trimmed_emissions); + Rank::::insert(netuid, trimmed_ranks); + Trust::::insert(netuid, trimmed_trust); + Active::::insert(netuid, trimmed_active); Consensus::::insert(netuid, trimmed_consensus); - - let incentive: Vec = Incentive::::get(netuid); - let trimmed_incentive: Vec = incentive.into_iter().take(max_n as usize).collect(); Incentive::::insert(netuid, trimmed_incentive); - - let dividends: Vec = Dividends::::get(netuid); - let trimmed_dividends: Vec = dividends.into_iter().take(max_n as usize).collect(); Dividends::::insert(netuid, trimmed_dividends); - - let lastupdate: Vec = LastUpdate::::get(netuid); - let trimmed_lastupdate: Vec = - lastupdate.into_iter().take(max_n as usize).collect(); LastUpdate::::insert(netuid, trimmed_lastupdate); - - let pruning_scores: Vec = PruningScores::::get(netuid); - let trimmed_pruning_scores: Vec = - pruning_scores.into_iter().take(max_n as usize).collect(); PruningScores::::insert(netuid, trimmed_pruning_scores); - - let vtrust: Vec = ValidatorTrust::::get(netuid); - let trimmed_vtrust: Vec = vtrust.into_iter().take(max_n as usize).collect(); ValidatorTrust::::insert(netuid, trimmed_vtrust); - - let vpermit: Vec = ValidatorPermit::::get(netuid); - let trimmed_vpermit: Vec = vpermit.into_iter().take(max_n as usize).collect(); ValidatorPermit::::insert(netuid, trimmed_vpermit); - - let stake_weight: Vec = StakeWeight::::get(netuid); - let trimmed_stake_weight: Vec = - stake_weight.into_iter().take(max_n as usize).collect(); StakeWeight::::insert(netuid, trimmed_stake_weight); - for uid in max_n..current_n { - // Trim UIDs and Keys by removing entries with UID >= max_n (since UIDs are 0-indexed) - // UIDs range from 0 to current_n-1, so we remove UIDs from max_n to current_n-1 - if let Ok(hotkey) = Keys::::try_get(netuid, uid) { - Uids::::remove(netuid, &hotkey); - // Remove IsNetworkMember association for the hotkey - IsNetworkMember::::remove(&hotkey, netuid); - // Remove last hotkey emission for the hotkey - LastHotkeyEmissionOnNetuid::::remove(&hotkey, netuid); - // Remove alpha dividends for the hotkey - AlphaDividendsPerSubnet::::remove(netuid, &hotkey); - // Remove tao dividends for the hotkey - TaoDividendsPerSubnet::::remove(netuid, &hotkey); - // Trim axons, certificates, and prometheus info for removed hotkeys - Axons::::remove(netuid, &hotkey); - NeuronCertificates::::remove(netuid, &hotkey); - Prometheus::::remove(netuid, &hotkey); - } - #[allow(unknown_lints)] - Keys::::remove(netuid, uid); - // Remove block at registration for the uid - BlockAtRegistration::::remove(netuid, uid); - // Remove entire weights and bonds entries for removed UIDs - Weights::::remove(netuid, uid); - Bonds::::remove(netuid, uid); - } - - // Trim weight and bond connections to removed UIDs for remaining neurons - // UIDs 0 to max_n-1 are kept, so we iterate through these valid UIDs - for uid in 0..max_n { - Weights::::mutate(netuid, uid, |weights| { - weights.retain(|(target_uid, _)| *target_uid < max_n); + // Create mapping from old uid to new compressed uid + // This is needed to update connections (weights and bonds) with correct uid references + let old_to_new_uid: BTreeMap = trimmed_uids + .iter() + .enumerate() + .map(|(new_uid, &old_uid)| (old_uid, new_uid)) + .collect(); + + // Update connections (weights and bonds) for each kept uid + // This involves three operations per uid: + // 1. Swap the uid storage to the new compressed position + // 2. Update all connections to reference the new compressed uids + // 3. Clear the connections to the trimmed uids + for (old_uid, new_uid) in &old_to_new_uid { + // Swap uid specific storage items to new compressed positions + Keys::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); + BlockAtRegistration::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); + + // Swap to new position and remap all target uids + Weights::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); + Weights::::mutate(netuid, *new_uid as u16, |weights| { + weights.retain_mut(|(target_uid, _weight)| { + if let Some(new_target_uid) = old_to_new_uid.get(&(*target_uid as usize)) { + *target_uid = *new_target_uid as u16; + true + } else { + false + } + }) }); - Bonds::::mutate(netuid, uid, |bonds| { - bonds.retain(|(target_uid, _)| *target_uid < max_n); + + // Swap to new position and remap all target uids + Bonds::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); + Bonds::::mutate(netuid, *new_uid as u16, |bonds| { + bonds.retain_mut(|(target_uid, _bond)| { + if let Some(new_target_uid) = old_to_new_uid.get(&(*target_uid as usize)) { + *target_uid = *new_target_uid as u16; + true + } else { + false + } + }) }); } - // Update the subnetwork size + // Update the subnet's uid count to reflect the new maximum SubnetworkN::::insert(netuid, max_n); } - // --- Ok and done. Ok(()) } diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index d011983383..30efd9bfd3 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -202,7 +202,6 @@ parameter_types! { pub const InitialRAORecycledForRegistration: u64 = 0; pub const InitialSenateRequiredStakePercentage: u64 = 2; // 2 percent of total stake pub const InitialNetworkImmunityPeriod: u64 = 7200 * 7; - pub const InitialNetworkMinAllowedUids: u16 = 128; pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. @@ -436,7 +435,6 @@ impl crate::Config for Test { type InitialRAORecycledForRegistration = InitialRAORecycledForRegistration; type InitialSenateRequiredStakePercentage = InitialSenateRequiredStakePercentage; type InitialNetworkImmunityPeriod = InitialNetworkImmunityPeriod; - type InitialNetworkMinAllowedUids = InitialNetworkMinAllowedUids; type InitialNetworkMinLockCost = InitialNetworkMinLockCost; type InitialSubnetOwnerCut = InitialSubnetOwnerCut; type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 3248c45e60..2871f6a792 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -194,7 +194,6 @@ parameter_types! { pub const InitialRAORecycledForRegistration: u64 = 0; pub const InitialSenateRequiredStakePercentage: u64 = 2; // 2 percent of total stake pub const InitialNetworkImmunityPeriod: u64 = 7200 * 7; - pub const InitialNetworkMinAllowedUids: u16 = 128; pub const InitialNetworkMinLockCost: u64 = 100_000_000_000; pub const InitialSubnetOwnerCut: u16 = 0; // 0%. 100% of rewards go to validators + miners. pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. @@ -270,7 +269,6 @@ impl pallet_subtensor::Config for Test { type InitialRAORecycledForRegistration = InitialRAORecycledForRegistration; type InitialSenateRequiredStakePercentage = InitialSenateRequiredStakePercentage; type InitialNetworkImmunityPeriod = InitialNetworkImmunityPeriod; - type InitialNetworkMinAllowedUids = InitialNetworkMinAllowedUids; type InitialNetworkMinLockCost = InitialNetworkMinLockCost; type InitialSubnetOwnerCut = InitialSubnetOwnerCut; type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index cc9c02eca3..3833eac796 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1140,7 +1140,7 @@ parameter_types! { pub const SubtensorInitialRAORecycledForRegistration: u64 = 0; // 0 rao pub const SubtensorInitialSenateRequiredStakePercentage: u64 = 1; // 1 percent of total stake pub const SubtensorInitialNetworkImmunity: u64 = 7 * 7200; - pub const SubtensorInitialMinAllowedUids: u16 = 128; + pub const SubtensorInitialMinAllowedUids: u16 = 256; pub const SubtensorInitialMinLockCost: u64 = 1_000_000_000_000; // 1000 TAO pub const SubtensorInitialSubnetOwnerCut: u16 = 11_796; // 18 percent // pub const SubtensorInitialSubnetLimit: u16 = 12; // (DEPRECATED) @@ -1216,7 +1216,6 @@ impl pallet_subtensor::Config for Runtime { type InitialRAORecycledForRegistration = SubtensorInitialRAORecycledForRegistration; type InitialSenateRequiredStakePercentage = SubtensorInitialSenateRequiredStakePercentage; type InitialNetworkImmunityPeriod = SubtensorInitialNetworkImmunity; - type InitialNetworkMinAllowedUids = SubtensorInitialMinAllowedUids; type InitialNetworkMinLockCost = SubtensorInitialMinLockCost; type InitialNetworkLockReductionInterval = SubtensorInitialNetworkLockReductionInterval; type InitialSubnetOwnerCut = SubtensorInitialSubnetOwnerCut; From 15a39251b44e4a03b4a78051dd7c7885c16a4874 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 11:39:27 -0300 Subject: [PATCH 12/39] add sudo set min allowed uids --- pallets/admin-utils/src/benchmarking.rs | 11 ++++ pallets/admin-utils/src/lib.rs | 35 +++++++++++++ pallets/admin-utils/src/tests/mod.rs | 64 +++++++++++++++++++++++ pallets/subtensor/src/macros/events.rs | 3 ++ pallets/subtensor/src/subnets/uids.rs | 4 +- pallets/subtensor/src/tests/mock.rs | 4 +- pallets/subtensor/src/utils/misc.rs | 8 +++ pallets/transaction-fee/src/tests/mock.rs | 4 +- runtime/src/lib.rs | 1 + 9 files changed, 130 insertions(+), 4 deletions(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 3e42763f68..919591ba39 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -203,6 +203,17 @@ mod benchmarks { _(RawOrigin::Root, 1u16.into()/*netuid*/, 3u16/*kappa*/)/*set_kappa*/; } + #[benchmark] + fn sudo_set_min_allowed_uids() { + pallet_subtensor::Pallet::::init_new_network( + 1u16.into(), /*netuid*/ + 1u16, /*tempo*/ + ); + + #[extrinsic_call] + _(RawOrigin::Root, 1u16.into()/*netuid*/, 32u16/*max_allowed_uids*/)/*sudo_set_max_allowed_uids*/; + } + #[benchmark] fn sudo_set_max_allowed_uids() { pallet_subtensor::Pallet::::init_new_network( diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 5a49796e30..66f33d442c 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -109,6 +109,10 @@ pub mod pallet { NegativeSigmoidSteepness, /// Value not in allowed bounds. ValueNotInBounds, + /// The minimum allowed UIDs must be less than the current number of UIDs in the subnet. + MinAllowedUidsGreaterThanCurrentUids, + /// The minimum allowed UIDs must be less than the maximum allowed UIDs. + MinAllowedUidsGreaterThanMaxAllowedUids, } /// Enum for specifying the type of precompile operation. #[derive( @@ -1654,6 +1658,37 @@ pub mod pallet { pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; Ok(()) } + + /// The extrinsic sets the minimum allowed UIDs for a subnet. + /// It is only callable by the root account. + #[pallet::call_index(75)] + #[pallet::weight(Weight::from_parts(18_800_000, 0) + .saturating_add(::DbWeight::get().reads(2_u64)) + .saturating_add(::DbWeight::get().writes(1_u64)))] + pub fn sudo_set_min_allowed_uids( + origin: OriginFor, + netuid: NetUid, + min_allowed_uids: u16, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + ensure!( + min_allowed_uids < pallet_subtensor::Pallet::::get_max_allowed_uids(netuid), + Error::::MinAllowedUidsGreaterThanMaxAllowedUids + ); + ensure!( + min_allowed_uids < pallet_subtensor::Pallet::::get_subnetwork_n(netuid), + Error::::MinAllowedUidsGreaterThanCurrentUids + ); + pallet_subtensor::Pallet::::set_min_allowed_uids(netuid, min_allowed_uids); + log::debug!( + "MinAllowedUidsSet( netuid: {netuid:?} min_allowed_uids: {min_allowed_uids:?} ) " + ); + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index ccce3a1e49..0977fadca6 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2297,3 +2297,67 @@ fn test_trim_to_max_allowed_uids() { ); }); } + +#[test] +fn test_sudo_set_min_allowed_uids() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + let to_be_set: u16 = 8; + add_network(netuid, 10); + MaxRegistrationsPerBlock::::insert(netuid, 256); + TargetRegistrationsPerInterval::::insert(netuid, 256); + + // Register some neurons + for i in 0..=16 { + register_ok_neuron(netuid, U256::from(i * 1000), U256::from(i * 1000 + i), 0); + } + + // Normal case + assert_ok!(AdminUtils::sudo_set_min_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + assert_eq!(SubtensorModule::get_min_allowed_uids(netuid), to_be_set); + + // Non root + assert_err!( + AdminUtils::sudo_set_min_allowed_uids( + <::RuntimeOrigin>::signed(U256::from(0)), + netuid, + to_be_set + ), + DispatchError::BadOrigin + ); + + // Non existent subnet + assert_err!( + AdminUtils::sudo_set_min_allowed_uids( + <::RuntimeOrigin>::root(), + NetUid::from(42), + to_be_set + ), + Error::::SubnetDoesNotExist + ); + + // Min allowed uids greater than max allowed uids + assert_err!( + AdminUtils::sudo_set_min_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + SubtensorModule::get_max_allowed_uids(netuid) + 1 + ), + Error::::MinAllowedUidsGreaterThanMaxAllowedUids + ); + + // Min allowed uids greater than current uids + assert_err!( + AdminUtils::sudo_set_min_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + SubtensorModule::get_subnetwork_n(netuid) + 1 + ), + Error::::MinAllowedUidsGreaterThanCurrentUids + ); + }); +} diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 2fab5ecdb4..bc3b2c999d 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -413,5 +413,8 @@ mod events { /// - **netuid**: The network identifier. /// - **who**: The account ID of the user revealing the weights. TimelockedWeightsRevealed(NetUid, T::AccountId), + + /// The minimum allowed UIDs for a subnet have been set. + MinAllowedUidsSet(NetUid, u16), } } diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 3d33f68bf3..e8353f58df 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -1,7 +1,7 @@ use super::*; use frame_support::storage::IterableStorageDoubleMap; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; -use sp_std::vec; +use sp_std::{cmp, vec}; use subtensor_runtime_common::NetUid; impl Pallet { @@ -137,7 +137,7 @@ impl Pallet { .into_iter() .enumerate() .collect::>(); - emissions.sort_by_key(|(_, emission)| std::cmp::Reverse(*emission)); + emissions.sort_by_key(|(_, emission)| cmp::Reverse(*emission)); // Remove uids from the end (lowest emitters) until we reach the new maximum let mut removed_uids = BTreeSet::new(); diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 30efd9bfd3..0485c0d19d 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -164,7 +164,8 @@ parameter_types! { pub const InitialTempo: u16 = 360; pub const SelfOwnership: u64 = 2; pub const InitialImmunityPeriod: u16 = 2; - pub const InitialMaxAllowedUids: u16 = 2; + pub const InitialMinAllowedUids: u16 = 2; + pub const InitialMaxAllowedUids: u16 = 4; pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialBondsPenalty:u16 = u16::MAX; pub const InitialBondsResetOn: bool = false; @@ -404,6 +405,7 @@ impl crate::Config for Test { type InitialRho = InitialRho; type InitialAlphaSigmoidSteepness = InitialAlphaSigmoidSteepness; type InitialKappa = InitialKappa; + type InitialMinAllowedUids = InitialMinAllowedUids; type InitialMaxAllowedUids = InitialMaxAllowedUids; type InitialValidatorPruneLen = InitialValidatorPruneLen; type InitialScalingLawPower = InitialScalingLawPower; diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index f64962f094..5ee88ef3ca 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -459,6 +459,14 @@ impl Pallet { Self::deposit_event(Event::MinAllowedWeightSet(netuid, min_allowed_weights)); } + pub fn get_min_allowed_uids(netuid: NetUid) -> u16 { + MinAllowedUids::::get(netuid) + } + pub fn set_min_allowed_uids(netuid: NetUid, min_allowed: u16) { + MinAllowedUids::::insert(netuid, min_allowed); + Self::deposit_event(Event::MinAllowedUidsSet(netuid, min_allowed)); + } + pub fn get_max_allowed_uids(netuid: NetUid) -> u16 { MaxAllowedUids::::get(netuid) } diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 2871f6a792..8f9eb729a3 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -156,7 +156,8 @@ parameter_types! { pub const InitialTempo: u16 = 0; pub const SelfOwnership: u64 = 2; pub const InitialImmunityPeriod: u16 = 2; - pub const InitialMaxAllowedUids: u16 = 2; + pub const InitialMinAllowedUids: u16 = 2; + pub const InitialMaxAllowedUids: u16 = 4; pub const InitialBondsMovingAverage: u64 = 900_000; pub const InitialBondsPenalty: u16 = u16::MAX; pub const InitialBondsResetOn: bool = false; @@ -238,6 +239,7 @@ impl pallet_subtensor::Config for Test { type InitialRho = InitialRho; type InitialAlphaSigmoidSteepness = InitialAlphaSigmoidSteepness; type InitialKappa = InitialKappa; + type InitialMinAllowedUids = InitialMinAllowedUids; type InitialMaxAllowedUids = InitialMaxAllowedUids; type InitialValidatorPruneLen = InitialValidatorPruneLen; type InitialScalingLawPower = InitialScalingLawPower; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3833eac796..71ca76548e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1176,6 +1176,7 @@ impl pallet_subtensor::Config for Runtime { type InitialRho = SubtensorInitialRho; type InitialAlphaSigmoidSteepness = SubtensorInitialAlphaSigmoidSteepness; type InitialKappa = SubtensorInitialKappa; + type InitialMinAllowedUids = SubtensorInitialMinAllowedUids; type InitialMaxAllowedUids = SubtensorInitialMaxAllowedUids; type InitialBondsMovingAverage = SubtensorInitialBondsMovingAverage; type InitialBondsPenalty = SubtensorInitialBondsPenalty; From ecc7f13ba34f057bd3f4cbfdbf3370b93273e367 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 11:54:03 -0300 Subject: [PATCH 13/39] commit Cargo.lock --- pallets/admin-utils/src/tests/mod.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 0977fadca6..968b65a486 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2152,8 +2152,8 @@ fn test_trim_to_max_allowed_uids() { for target_uid in 0..max_n { if target_uid != uid { // Use some non-zero values to make the test more meaningful - let weight_value = ((uid + target_uid) % 1000) as u16; - let bond_value = ((uid * target_uid) % 1000) as u16; + let weight_value = (uid + target_uid) % 1000; + let bond_value = (uid * target_uid) % 1000; weights.push((target_uid, weight_value)); bonds.push((target_uid, bond_value)); } @@ -2225,20 +2225,20 @@ fn test_trim_to_max_allowed_uids() { U256::from(16000), ]; for hotkey in trimmed_hotkeys { - assert!(!Uids::::contains_key(netuid, &hotkey)); - assert!(!IsNetworkMember::::contains_key(&hotkey, netuid)); + assert!(!Uids::::contains_key(netuid, hotkey)); + assert!(!IsNetworkMember::::contains_key(hotkey, netuid)); assert!(!LastHotkeyEmissionOnNetuid::::contains_key( - &hotkey, netuid + hotkey, netuid )); assert!(!AlphaDividendsPerSubnet::::contains_key( - netuid, &hotkey + netuid, hotkey )); assert!(!TaoDividendsPerSubnet::::contains_key( - netuid, &hotkey + netuid, hotkey )); - assert!(!Axons::::contains_key(netuid, &hotkey)); - assert!(!NeuronCertificates::::contains_key(netuid, &hotkey)); - assert!(!Prometheus::::contains_key(netuid, &hotkey)); + assert!(!Axons::::contains_key(netuid, hotkey)); + assert!(!NeuronCertificates::::contains_key(netuid, hotkey)); + assert!(!Prometheus::::contains_key(netuid, hotkey)); } // Ensure trimmed uids weights and bonds have been cleared From 30f3f0dea2285685f645aa1070b8acefee013751 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 11:56:07 -0300 Subject: [PATCH 14/39] cargo fmt --- pallets/admin-utils/src/tests/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 968b65a486..fa9f31a935 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2233,9 +2233,7 @@ fn test_trim_to_max_allowed_uids() { assert!(!AlphaDividendsPerSubnet::::contains_key( netuid, hotkey )); - assert!(!TaoDividendsPerSubnet::::contains_key( - netuid, hotkey - )); + assert!(!TaoDividendsPerSubnet::::contains_key(netuid, hotkey)); assert!(!Axons::::contains_key(netuid, hotkey)); assert!(!NeuronCertificates::::contains_key(netuid, hotkey)); assert!(!Prometheus::::contains_key(netuid, hotkey)); From aa4c566a94f8e9d5120489f5662b7328706a8ff7 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 11:57:12 -0300 Subject: [PATCH 15/39] commit Cargo.lock --- pallets/subtensor/src/subnets/uids.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index e8353f58df..964d17a136 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -170,6 +170,7 @@ impl Pallet { } // Remove all storage items associated with this uid + #[allow(unknown_lints)] Keys::::remove(netuid, uid as u16); BlockAtRegistration::::remove(netuid, uid as u16); Weights::::remove(netuid, uid as u16); From b7e434ccae0cd6d3f10c5f2360467e3ac30af562 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 12:10:50 -0300 Subject: [PATCH 16/39] fix merge --- pallets/admin-utils/src/benchmarking.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 919591ba39..871ceeb69e 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -356,6 +356,17 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Root, 5u16/*version*/)/*sudo_set_commit_reveal_version()*/; } + + #[benchmark] + fn sudo_set_owner_immune_neuron_limit() { + pallet_subtensor::Pallet::::init_new_network( + 1u16.into(), /*netuid*/ + 1u16, /*sudo_tempo*/ + ); + + #[extrinsic_call] + _(RawOrigin::Root, 1u16.into()/*netuid*/, 5u16/*immune_neurons*/)/*sudo_set_owner_immune_neuron_limit()*/; + } #[benchmark] fn sudo_trim_to_max_allowed_uids() { From 26798bd9a11faac5b5afd33e89ab80a2df77e231 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 12:14:25 -0300 Subject: [PATCH 17/39] fix merge 2 --- pallets/admin-utils/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 871ceeb69e..a00058d2f7 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -356,7 +356,7 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Root, 5u16/*version*/)/*sudo_set_commit_reveal_version()*/; } - + #[benchmark] fn sudo_set_owner_immune_neuron_limit() { pallet_subtensor::Pallet::::init_new_network( From c19b71fd90d14f79cff1b5405479c6654d899a0a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 12:27:56 -0300 Subject: [PATCH 18/39] cargo fmt --- pallets/admin-utils/src/benchmarking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index a00058d2f7..7a9ff00e9e 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -366,7 +366,7 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Root, 1u16.into()/*netuid*/, 5u16/*immune_neurons*/)/*sudo_set_owner_immune_neuron_limit()*/; - } + } #[benchmark] fn sudo_trim_to_max_allowed_uids() { From 5dc32fd1a4bf15cf29799d44f9675b79024c6674 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 20:48:51 -0300 Subject: [PATCH 19/39] added max immune percentage --- pallets/admin-utils/src/tests/mock.rs | 3 + pallets/admin-utils/src/tests/mod.rs | 95 +++++++++++++++++++++++ pallets/subtensor/src/macros/config.rs | 3 + pallets/subtensor/src/subnets/uids.rs | 15 ++++ pallets/subtensor/src/tests/mock.rs | 2 + pallets/transaction-fee/src/tests/mock.rs | 2 + runtime/src/lib.rs | 2 + 7 files changed, 122 insertions(+) diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index be2052a212..9a0ffb6896 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -16,6 +16,7 @@ use sp_runtime::{ BuildStorage, KeyTypeId, Perbill, testing::TestXt, traits::{BlakeTwo256, ConstU32, IdentityLookup}, + Percent, }; use sp_std::cmp::Ordering; use sp_weights::Weight; @@ -151,6 +152,7 @@ parameter_types! { pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000; pub const HotkeySwapOnSubnetInterval: u64 = 7 * 24 * 60 * 60 / 12; // 7 days pub const LeaseDividendsDistributionInterval: u32 = 100; // 100 blocks + pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80); } impl pallet_subtensor::Config for Test { @@ -228,6 +230,7 @@ impl pallet_subtensor::Config for Test { type ProxyInterface = (); type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; type GetCommitments = (); + type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; } parameter_types! { diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index b6dff69ef4..e9187cb437 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2296,6 +2296,101 @@ fn test_trim_to_max_allowed_uids() { }); } +#[test] +fn test_trim_to_max_allowed_uids_too_many_immune() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + let sn_owner = U256::from(1); + add_network(netuid, 10); + SubnetOwner::::insert(netuid, sn_owner); + MaxRegistrationsPerBlock::::insert(netuid, 256); + TargetRegistrationsPerInterval::::insert(netuid, 256); + ImmuneOwnerUidsLimit::::insert(netuid, 2); + MinAllowedUids::::set(netuid, 4); + + // Add 5 neurons + let max_n = 5; + for i in 1..=max_n { + let n = i * 1000; + register_ok_neuron(netuid, U256::from(n), U256::from(n + i), 0); + } + + // Run some blocks to ensure stake weights are set + run_to_block((ImmunityPeriod::::get(netuid) + 1).into()); + + // Set owner immune uids (2 UIDs) by adding them to OwnedHotkeys + let owner_hotkey1 = U256::from(1000); + let owner_hotkey2 = U256::from(2000); + OwnedHotkeys::::insert(sn_owner, vec![owner_hotkey1, owner_hotkey2]); + Keys::::insert(netuid, 0, owner_hotkey1); + Uids::::insert(netuid, owner_hotkey1, 0); + Keys::::insert(netuid, 1, owner_hotkey2); + Uids::::insert(netuid, owner_hotkey2, 1); + + // Set temporally immune uids (2 UIDs) to make total immune count 4 out of 5 (80%) + // Set their registration block to current block to make them temporally immune + let current_block = frame_system::Pallet::::block_number(); + for uid in 2..4 { + let hotkey = U256::from(uid * 1000 + 1000); + Keys::::insert(netuid, uid, hotkey); + Uids::::insert(netuid, hotkey, uid); + BlockAtRegistration::::insert(netuid, uid, current_block); + } + + // Try to trim to 4 UIDs - this should fail because 4/4 = 100% immune (>= 80%) + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + 4 + ), + pallet_subtensor::Error::::InvalidValue + ); + + // Try to trim to 3 UIDs - this should also fail because 4/3 > 80% immune (>= 80%) + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + 3 + ), + pallet_subtensor::Error::::InvalidValue + ); + + // Now test a scenario where trimming should succeed + // Remove one immune UID to make it 3 immune out of 4 total + let uid_to_remove = 3; + let hotkey_to_remove = U256::from(uid_to_remove * 1000 + 1000); + #[allow(unknown_lints)] + Keys::::remove(netuid, uid_to_remove); + Uids::::remove(netuid, hotkey_to_remove); + BlockAtRegistration::::remove(netuid, uid_to_remove); + + // Now we have 3 immune out of 4 total UIDs + // Try to trim to 3 UIDs - this should succeed because 3/3 = 100% immune, but that's exactly 80% + // Wait, 100% is > 80%, so this should fail. Let me test with a scenario where we have fewer immune UIDs + + // Remove another immune UID to make it 2 immune out of 3 total + let uid_to_remove2 = 2; + let hotkey_to_remove2 = U256::from(uid_to_remove2 * 1000 + 1000); + #[allow(unknown_lints)] + Keys::::remove(netuid, uid_to_remove2); + Uids::::remove(netuid, hotkey_to_remove2); + BlockAtRegistration::::remove(netuid, uid_to_remove2); + + // Now we have 2 immune out of 2 total UIDs + // Try to trim to 1 UID - this should fail because 2/1 is impossible, but the check prevents it + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + 1 + ), + pallet_subtensor::Error::::InvalidValue + ); + }); +} + #[test] fn test_sudo_set_min_allowed_uids() { new_test_ext().execute_with(|| { diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 6bcb382631..cb6af29728 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -254,5 +254,8 @@ mod config { /// Number of blocks between dividends distribution. #[pallet::constant] type LeaseDividendsDistributionInterval: Get>; + /// Maximum percentage of immune UIDs. + #[pallet::constant] + type MaxImmuneUidsPercentage: Get; } } diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 964d17a136..937d40abd1 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -1,5 +1,6 @@ use super::*; use frame_support::storage::IterableStorageDoubleMap; +use sp_runtime::Percent; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use sp_std::{cmp, vec}; use subtensor_runtime_common::NetUid; @@ -131,6 +132,20 @@ impl Pallet { let current_n = Self::get_subnetwork_n(netuid); if current_n > max_n { + // Count the number of immune UIDs + let mut immune_count = 0; + for uid in 0..current_n { + if owner_uids.contains(&(uid as u16)) + || Self::get_neuron_is_immune(netuid, uid as u16) + { + immune_count += 1; + } + } + + // Ensure the number of immune UIDs is less than 80% + let immune_percentage = Percent::from_rational(immune_count, max_n); + ensure!(immune_percentage < T::MaxImmuneUidsPercentage::get(), Error::::InvalidValue); + // Get all emissions with their UIDs and sort by emission (descending) // This ensures we keep the highest emitters and remove the lowest ones let mut emissions = Emission::::get(netuid) diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 300a65a436..d91ee9c8af 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -223,6 +223,7 @@ parameter_types! { pub const HotkeySwapOnSubnetInterval: u64 = 15; // 15 block, should be bigger than subnet number, then trigger clean up for all subnets pub const MaxContributorsPerLeaseToRemove: u32 = 3; pub const LeaseDividendsDistributionInterval: u32 = 100; + pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80); } // Configure collective pallet for council @@ -459,6 +460,7 @@ impl crate::Config for Test { type ProxyInterface = FakeProxier; type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; type GetCommitments = (); + type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; } // Swap-related parameter types diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index c16fc87d82..ee3da7f0a4 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -216,6 +216,7 @@ parameter_types! { pub const InitialKeySwapOnSubnetCost: u64 = 10_000_000; pub const HotkeySwapOnSubnetInterval: u64 = 7 * 24 * 60 * 60 / 12; // 7 days pub const LeaseDividendsDistributionInterval: u32 = 100; // 100 blocks + pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80); } impl pallet_subtensor::Config for Test { @@ -293,6 +294,7 @@ impl pallet_subtensor::Config for Test { type ProxyInterface = (); type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; type GetCommitments = (); + type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; } parameter_types! { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index ef470c048c..bdf97c29ca 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1169,6 +1169,7 @@ parameter_types! { pub const SubtensorInitialKeySwapOnSubnetCost: u64 = 1_000_000; // 0.001 TAO pub const HotkeySwapOnSubnetInterval : BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const LeaseDividendsDistributionInterval: BlockNumber = 100; // 100 blocks + pub const MaxImmuneUidsPercentage: Percent = Percent::from_percent(80); } impl pallet_subtensor::Config for Runtime { @@ -1246,6 +1247,7 @@ impl pallet_subtensor::Config for Runtime { type ProxyInterface = Proxier; type LeaseDividendsDistributionInterval = LeaseDividendsDistributionInterval; type GetCommitments = GetCommitmentsStruct; + type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage; } parameter_types! { From 5a55def9bcaddb414e99ea04be912e1796c2875d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 20:52:43 -0300 Subject: [PATCH 20/39] fix imports --- pallets/subtensor/src/tests/mock.rs | 1 + pallets/transaction-fee/src/tests/mock.rs | 1 + runtime/src/lib.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index d91ee9c8af..01246afeba 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -21,6 +21,7 @@ use sp_runtime::Perbill; use sp_runtime::{ BuildStorage, traits::{BlakeTwo256, IdentityLookup}, + Percent, }; use sp_std::{cell::RefCell, cmp::Ordering}; use subtensor_runtime_common::{NetUid, TaoCurrency}; diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index ee3da7f0a4..99840b789f 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -18,6 +18,7 @@ use sp_runtime::{ BuildStorage, KeyTypeId, Perbill, testing::TestXt, traits::{BlakeTwo256, ConstU32, IdentityLookup, One}, + Percent, }; use sp_std::cmp::Ordering; use sp_weights::Weight; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index bdf97c29ca..da21c582fe 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -50,6 +50,7 @@ use sp_core::{ use sp_runtime::Cow; use sp_runtime::generic::Era; use sp_runtime::{ + Percent, AccountId32, ApplyExtrinsicResult, ConsensusEngineId, generic, impl_opaque_keys, traits::{ AccountIdLookup, BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, One, From 0a033b2866a5ccbb2850291252ab03e6d83762be Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 20:54:31 -0300 Subject: [PATCH 21/39] cargo clippy --- pallets/subtensor/src/subnets/uids.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 937d40abd1..c477b6e7d5 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -135,8 +135,8 @@ impl Pallet { // Count the number of immune UIDs let mut immune_count = 0; for uid in 0..current_n { - if owner_uids.contains(&(uid as u16)) - || Self::get_neuron_is_immune(netuid, uid as u16) + if owner_uids.contains(&{ uid }) + || Self::get_neuron_is_immune(netuid, uid) { immune_count += 1; } From 246344131e7e5a99a9bf3b2b1870f0e2021332db Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 20:55:49 -0300 Subject: [PATCH 22/39] commit Cargo.lock --- pallets/subtensor/src/subnets/uids.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index c477b6e7d5..f37e6224b0 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -133,12 +133,12 @@ impl Pallet { if current_n > max_n { // Count the number of immune UIDs - let mut immune_count = 0; + let mut immune_count: u16 = 0; for uid in 0..current_n { if owner_uids.contains(&{ uid }) || Self::get_neuron_is_immune(netuid, uid) { - immune_count += 1; + immune_count = immune_count.saturating_add(1); } } From 9e27dd0fbbdc1e04e5a5bc5eadec52bc18d8d5a5 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 9 Sep 2025 20:58:02 -0300 Subject: [PATCH 23/39] cargo fmt --- pallets/admin-utils/src/tests/mock.rs | 3 +-- pallets/subtensor/src/subnets/uids.rs | 9 +++++---- pallets/subtensor/src/tests/mock.rs | 3 +-- pallets/transaction-fee/src/tests/mock.rs | 3 +-- runtime/src/lib.rs | 3 +-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 9a0ffb6896..e9a274470a 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -13,10 +13,9 @@ use sp_consensus_grandpa::AuthorityList as GrandpaAuthorityList; use sp_core::U256; use sp_core::{ConstU64, H256}; use sp_runtime::{ - BuildStorage, KeyTypeId, Perbill, + BuildStorage, KeyTypeId, Perbill, Percent, testing::TestXt, traits::{BlakeTwo256, ConstU32, IdentityLookup}, - Percent, }; use sp_std::cmp::Ordering; use sp_weights::Weight; diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index f37e6224b0..aaafaf8edc 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -135,16 +135,17 @@ impl Pallet { // Count the number of immune UIDs let mut immune_count: u16 = 0; for uid in 0..current_n { - if owner_uids.contains(&{ uid }) - || Self::get_neuron_is_immune(netuid, uid) - { + if owner_uids.contains(&{ uid }) || Self::get_neuron_is_immune(netuid, uid) { immune_count = immune_count.saturating_add(1); } } // Ensure the number of immune UIDs is less than 80% let immune_percentage = Percent::from_rational(immune_count, max_n); - ensure!(immune_percentage < T::MaxImmuneUidsPercentage::get(), Error::::InvalidValue); + ensure!( + immune_percentage < T::MaxImmuneUidsPercentage::get(), + Error::::InvalidValue + ); // Get all emissions with their UIDs and sort by emission (descending) // This ensures we keep the highest emitters and remove the lowest ones diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 01246afeba..f45b36dbc4 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -19,9 +19,8 @@ use pallet_collective::MemberCount; use sp_core::{ConstU64, Get, H256, U256, offchain::KeyTypeId}; use sp_runtime::Perbill; use sp_runtime::{ - BuildStorage, + BuildStorage, Percent, traits::{BlakeTwo256, IdentityLookup}, - Percent, }; use sp_std::{cell::RefCell, cmp::Ordering}; use subtensor_runtime_common::{NetUid, TaoCurrency}; diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index 99840b789f..8c6a064ef6 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -15,10 +15,9 @@ pub use pallet_subtensor::*; pub use sp_core::U256; use sp_core::{ConstU64, H256}; use sp_runtime::{ - BuildStorage, KeyTypeId, Perbill, + BuildStorage, KeyTypeId, Perbill, Percent, testing::TestXt, traits::{BlakeTwo256, ConstU32, IdentityLookup, One}, - Percent, }; use sp_std::cmp::Ordering; use sp_weights::Weight; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index da21c582fe..0c17e9b11b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -50,8 +50,7 @@ use sp_core::{ use sp_runtime::Cow; use sp_runtime::generic::Era; use sp_runtime::{ - Percent, - AccountId32, ApplyExtrinsicResult, ConsensusEngineId, generic, impl_opaque_keys, + AccountId32, ApplyExtrinsicResult, ConsensusEngineId, Percent, generic, impl_opaque_keys, traits::{ AccountIdLookup, BlakeTwo256, Block as BlockT, DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf, UniqueSaturatedInto, Verify, From e6fd948d3a156969aae018a813fe0d51b71c154a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Sep 2025 09:55:22 -0300 Subject: [PATCH 24/39] restore clear_neuron --- pallets/subtensor/src/subnets/uids.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index aaafaf8edc..36e3abaaa0 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -67,6 +67,17 @@ impl Pallet { Axons::::remove(netuid, old_hotkey); } + /// Resets the trust, emission, consensus, incentive, dividends of the neuron to default + pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { + let neuron_index: usize = neuron_uid.into(); + Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); + Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. + } + /// Appends the uid to the network. pub fn append_neuron(netuid: NetUid, new_hotkey: &T::AccountId, block_number: u64) { // 1. Get the next uid. This is always equal to subnetwork_n. @@ -98,18 +109,6 @@ impl Pallet { IsNetworkMember::::insert(new_hotkey.clone(), netuid, true); // Fill network is member. } - /// Clears (sets to default) the neuron map values fot a neuron when it is - /// removed from the subnet - pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { - let neuron_index: usize = neuron_uid.into(); - Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); - Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. - } - pub fn trim_to_max_allowed_uids(netuid: NetUid, max_n: u16) -> DispatchResult { // Reasonable limits ensure!( From f0b71885c8bd8fb0a93360f8b910430424af492c Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Sep 2025 10:01:32 -0300 Subject: [PATCH 25/39] restore clear_neuron 2 --- pallets/subtensor/src/subnets/uids.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 36e3abaaa0..5c5de832bd 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -18,6 +18,17 @@ impl Pallet { } } + /// Resets the trust, emission, consensus, incentive, dividends of the neuron to default + pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { + let neuron_index: usize = neuron_uid.into(); + Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); + Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. + } + /// Replace the neuron under this uid. pub fn replace_neuron( netuid: NetUid, @@ -67,17 +78,6 @@ impl Pallet { Axons::::remove(netuid, old_hotkey); } - /// Resets the trust, emission, consensus, incentive, dividends of the neuron to default - pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { - let neuron_index: usize = neuron_uid.into(); - Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); - Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. - } - /// Appends the uid to the network. pub fn append_neuron(netuid: NetUid, new_hotkey: &T::AccountId, block_number: u64) { // 1. Get the next uid. This is always equal to subnetwork_n. From 15ccf67abcdf47f2572aaff1fc411f691cc52c97 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Sep 2025 10:36:47 -0300 Subject: [PATCH 26/39] cargo fmt --- pallets/subtensor/src/macros/events.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index dc52e624b8..9d44fc58ef 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -435,7 +435,7 @@ mod events { /// UID-indexed array of miner incentive alpha; index equals UID. emissions: Vec, }, - + /// The minimum allowed UIDs for a subnet have been set. MinAllowedUidsSet(NetUid, u16), } From a027ba7060a769648c6b9bcafd193324dcb1042d Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Sep 2025 16:48:29 -0300 Subject: [PATCH 27/39] fix rate limit + call_index --- pallets/admin-utils/src/lib.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 07d54eee7e..9a0230d32e 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1908,7 +1908,7 @@ pub mod pallet { /// The trimming is done by sorting the UIDs by emission descending and then trimming /// the lowest emitters while preserving temporally and owner immune UIDs. The UIDs are /// then compressed to the left and storage is migrated to the new compressed UIDs. - #[pallet::call_index(74)] + #[pallet::call_index(78)] #[pallet::weight(Weight::from_parts(15_000_000, 0) .saturating_add(::DbWeight::get().reads(1_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] @@ -1917,24 +1917,25 @@ pub mod pallet { netuid: NetUid, max_n: u16, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; - if let Ok(RawOrigin::Signed(who)) = origin.into() { - ensure!( - pallet_subtensor::Pallet::::passes_rate_limit_on_subnet( - &TransactionType::SetMaxAllowedUIDS, - &who, - netuid, - ), - pallet_subtensor::Error::::TxRateLimitExceeded - ); - } + let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( + origin.clone(), + netuid, + &[TransactionType::SetMaxAllowedUIDS], + )?; + pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; + + pallet_subtensor::Pallet::::record_owner_rl( + maybe_owner, + netuid, + &[TransactionType::SetMaxAllowedUIDS], + ); Ok(()) } /// The extrinsic sets the minimum allowed UIDs for a subnet. /// It is only callable by the root account. - #[pallet::call_index(75)] + #[pallet::call_index(79)] #[pallet::weight(Weight::from_parts(18_800_000, 0) .saturating_add(::DbWeight::get().reads(2_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] @@ -1956,7 +1957,9 @@ pub mod pallet { min_allowed_uids < pallet_subtensor::Pallet::::get_subnetwork_n(netuid), Error::::MinAllowedUidsGreaterThanCurrentUids ); + pallet_subtensor::Pallet::::set_min_allowed_uids(netuid, min_allowed_uids); + log::debug!( "MinAllowedUidsSet( netuid: {netuid:?} min_allowed_uids: {min_allowed_uids:?} ) " ); From b2b96368f01bb0f15c97c006fb2f0a229a47ca49 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Sep 2025 16:48:44 -0300 Subject: [PATCH 28/39] refacto to handle subsubnet when trimming uids --- pallets/subtensor/src/subnets/uids.rs | 162 +++++++++++++++----------- 1 file changed, 94 insertions(+), 68 deletions(-) diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 7a7b6f67b2..c910c17336 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -148,11 +148,11 @@ impl Pallet { MaxAllowedUids::::insert(netuid, max_n); - let owner = SubnetOwner::::get(netuid); - let owner_uids = BTreeSet::from_iter(Self::get_immune_owner_uids(netuid, &owner)); let current_n = Self::get_subnetwork_n(netuid); - if current_n > max_n { + let owner = SubnetOwner::::get(netuid); + let owner_uids = BTreeSet::from_iter(Self::get_immune_owner_uids(netuid, &owner)); + // Count the number of immune UIDs let mut immune_count: u16 = 0; for uid in 0..current_n { @@ -167,7 +167,8 @@ impl Pallet { immune_percentage < T::MaxImmuneUidsPercentage::get(), Error::::InvalidValue ); - + + // Get all emissions with their UIDs and sort by emission (descending) // This ensures we keep the highest emitters and remove the lowest ones let mut emissions = Emission::::get(netuid) @@ -176,9 +177,9 @@ impl Pallet { .collect::>(); emissions.sort_by_key(|(_, emission)| cmp::Reverse(*emission)); - // Remove uids from the end (lowest emitters) until we reach the new maximum let mut removed_uids = BTreeSet::new(); let mut uids_left_to_process = current_n; + let subsubnets_count = SubsubnetCountCurrent::::get(netuid).into(); // Iterate from the end (lowest emitters) to the beginning for i in (0..current_n).rev() { @@ -187,15 +188,17 @@ impl Pallet { } if let Some((uid, _)) = emissions.get(i as usize).cloned() { + let neuron_uid = uid as u16; + // Skip subnet owner's or temporally immune uids - if owner_uids.contains(&(uid as u16)) - || Self::get_neuron_is_immune(netuid, uid as u16) + if owner_uids.contains(&neuron_uid) + || Self::get_neuron_is_immune(netuid, neuron_uid) { continue; } // Remove hotkey related storage items if hotkey exists - if let Ok(hotkey) = Keys::::try_get(netuid, uid as u16) { + if let Ok(hotkey) = Keys::::try_get(netuid, neuron_uid) { Uids::::remove(netuid, &hotkey); IsNetworkMember::::remove(&hotkey, netuid); LastHotkeyEmissionOnNetuid::::remove(&hotkey, netuid); @@ -208,10 +211,13 @@ impl Pallet { // Remove all storage items associated with this uid #[allow(unknown_lints)] - Keys::::remove(netuid, uid as u16); - BlockAtRegistration::::remove(netuid, uid as u16); - Weights::::remove(netuid, uid as u16); - Bonds::::remove(netuid, uid as u16); + Keys::::remove(netuid, neuron_uid); + BlockAtRegistration::::remove(netuid, neuron_uid); + for subid in 0..subsubnets_count { + let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); + Weights::::remove(netuid_index, neuron_uid); + Bonds::::remove(netuid_index, neuron_uid); + } // Remove from emissions array and track as removed emissions.remove(i.into()); @@ -233,9 +239,7 @@ impl Pallet { let trust = Trust::::get(netuid); let active = Active::::get(netuid); let consensus = Consensus::::get(netuid); - let incentive = Incentive::::get(netuid); let dividends = Dividends::::get(netuid); - let lastupdate = LastUpdate::::get(netuid); let pruning_scores = PruningScores::::get(netuid); let vtrust = ValidatorTrust::::get(netuid); let vpermit = ValidatorPermit::::get(netuid); @@ -243,32 +247,28 @@ impl Pallet { // Create trimmed arrays by extracting values for kept uids only // Pre-allocate vectors with exact capacity for efficiency - let mut trimmed_ranks = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_trust = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_active = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_consensus = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_incentive = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_dividends = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_lastupdate = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_pruning_scores = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_vtrust = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_vpermit = Vec::with_capacity(trimmed_uids.len()); - let mut trimmed_stake_weight = Vec::with_capacity(trimmed_uids.len()); + let len = trimmed_uids.len(); + let mut trimmed_ranks = Vec::with_capacity(len); + let mut trimmed_trust = Vec::with_capacity(len); + let mut trimmed_active = Vec::with_capacity(len); + let mut trimmed_consensus = Vec::with_capacity(len); + let mut trimmed_dividends = Vec::with_capacity(len); + let mut trimmed_pruning_scores = Vec::with_capacity(len); + let mut trimmed_vtrust = Vec::with_capacity(len); + let mut trimmed_vpermit = Vec::with_capacity(len); + let mut trimmed_stake_weight = Vec::with_capacity(len); // Single iteration to extract values for all kept uids - for &old_uid in &trimmed_uids { - trimmed_ranks.push(ranks.get(old_uid).cloned().unwrap_or_default()); - trimmed_trust.push(trust.get(old_uid).cloned().unwrap_or_default()); - trimmed_active.push(active.get(old_uid).cloned().unwrap_or_default()); - trimmed_consensus.push(consensus.get(old_uid).cloned().unwrap_or_default()); - trimmed_incentive.push(incentive.get(old_uid).cloned().unwrap_or_default()); - trimmed_dividends.push(dividends.get(old_uid).cloned().unwrap_or_default()); - trimmed_lastupdate.push(lastupdate.get(old_uid).cloned().unwrap_or_default()); - trimmed_pruning_scores - .push(pruning_scores.get(old_uid).cloned().unwrap_or_default()); - trimmed_vtrust.push(vtrust.get(old_uid).cloned().unwrap_or_default()); - trimmed_vpermit.push(vpermit.get(old_uid).cloned().unwrap_or_default()); - trimmed_stake_weight.push(stake_weight.get(old_uid).cloned().unwrap_or_default()); + for &uid in &trimmed_uids { + trimmed_ranks.push(ranks.get(uid).cloned().unwrap_or_default()); + trimmed_trust.push(trust.get(uid).cloned().unwrap_or_default()); + trimmed_active.push(active.get(uid).cloned().unwrap_or_default()); + trimmed_consensus.push(consensus.get(uid).cloned().unwrap_or_default()); + trimmed_dividends.push(dividends.get(uid).cloned().unwrap_or_default()); + trimmed_pruning_scores.push(pruning_scores.get(uid).cloned().unwrap_or_default()); + trimmed_vtrust.push(vtrust.get(uid).cloned().unwrap_or_default()); + trimmed_vpermit.push(vpermit.get(uid).cloned().unwrap_or_default()); + trimmed_stake_weight.push(stake_weight.get(uid).cloned().unwrap_or_default()); } // Update storage with trimmed arrays @@ -277,14 +277,29 @@ impl Pallet { Trust::::insert(netuid, trimmed_trust); Active::::insert(netuid, trimmed_active); Consensus::::insert(netuid, trimmed_consensus); - Incentive::::insert(netuid, trimmed_incentive); Dividends::::insert(netuid, trimmed_dividends); - LastUpdate::::insert(netuid, trimmed_lastupdate); PruningScores::::insert(netuid, trimmed_pruning_scores); ValidatorTrust::::insert(netuid, trimmed_vtrust); ValidatorPermit::::insert(netuid, trimmed_vpermit); StakeWeight::::insert(netuid, trimmed_stake_weight); + // Update incentives/lastupdates for subsubnets + for subid in 0..subsubnets_count { + let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); + let incentive = Incentive::::get(netuid_index); + let lastupdate = LastUpdate::::get(netuid_index); + let mut trimmed_incentive = Vec::with_capacity(trimmed_uids.len()); + let mut trimmed_lastupdate = Vec::with_capacity(trimmed_uids.len()); + + for uid in &trimmed_uids { + trimmed_incentive.push(incentive.get(*uid).cloned().unwrap_or_default()); + trimmed_lastupdate.push(lastupdate.get(*uid).cloned().unwrap_or_default()); + } + + Incentive::::insert(netuid_index, trimmed_incentive); + LastUpdate::::insert(netuid_index, trimmed_lastupdate); + } + // Create mapping from old uid to new compressed uid // This is needed to update connections (weights and bonds) with correct uid references let old_to_new_uid: BTreeMap = trimmed_uids @@ -299,35 +314,46 @@ impl Pallet { // 2. Update all connections to reference the new compressed uids // 3. Clear the connections to the trimmed uids for (old_uid, new_uid) in &old_to_new_uid { - // Swap uid specific storage items to new compressed positions - Keys::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); - BlockAtRegistration::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); - - // Swap to new position and remap all target uids - Weights::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); - Weights::::mutate(netuid, *new_uid as u16, |weights| { - weights.retain_mut(|(target_uid, _weight)| { - if let Some(new_target_uid) = old_to_new_uid.get(&(*target_uid as usize)) { - *target_uid = *new_target_uid as u16; - true - } else { - false - } - }) - }); + let old_neuron_uid = *old_uid as u16; + let new_neuron_uid = *new_uid as u16; - // Swap to new position and remap all target uids - Bonds::::swap(netuid, *old_uid as u16, netuid, *new_uid as u16); - Bonds::::mutate(netuid, *new_uid as u16, |bonds| { - bonds.retain_mut(|(target_uid, _bond)| { - if let Some(new_target_uid) = old_to_new_uid.get(&(*target_uid as usize)) { - *target_uid = *new_target_uid as u16; - true - } else { - false - } - }) - }); + // Swap uid specific storage items to new compressed positions + Keys::::swap(netuid, old_neuron_uid, netuid, new_neuron_uid); + BlockAtRegistration::::swap(netuid, old_neuron_uid, netuid, new_neuron_uid); + + for subid in 0..subsubnets_count { + let netuid_index = Self::get_subsubnet_storage_index(netuid, subid.into()); + + // Swap to new position and remap all target uids + Weights::::swap(netuid_index, old_neuron_uid, netuid_index, new_neuron_uid); + Weights::::mutate(netuid_index, new_neuron_uid, |weights| { + weights.retain_mut(|(target_uid, _weight)| { + if let Some(new_target_uid) = + old_to_new_uid.get(&(*target_uid as usize)) + { + *target_uid = *new_target_uid as u16; + true + } else { + false + } + }) + }); + + // Swap to new position and remap all target uids + Bonds::::swap(netuid_index, old_neuron_uid, netuid_index, new_neuron_uid); + Bonds::::mutate(netuid_index, new_neuron_uid, |bonds| { + bonds.retain_mut(|(target_uid, _bond)| { + if let Some(new_target_uid) = + old_to_new_uid.get(&(*target_uid as usize)) + { + *target_uid = *new_target_uid as u16; + true + } else { + false + } + }) + }); + } } // Update the subnet's uid count to reflect the new maximum From 20ef94a394a805b05d072359e0bda6815098ce5f Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Wed, 10 Sep 2025 17:07:38 -0300 Subject: [PATCH 29/39] fixed tests --- pallets/admin-utils/src/tests/mod.rs | 73 +++++++++++++++++---------- pallets/subtensor/src/subnets/uids.rs | 3 +- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index b727c499ac..bbf66fb1bd 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2288,6 +2288,9 @@ fn test_trim_to_max_allowed_uids() { ImmuneOwnerUidsLimit::::insert(netuid, 2); // We set a low value here to make testing easier MinAllowedUids::::set(netuid, 4); + // We define 4 subsubnets + let subsubnet_count = SubId::from(4); + SubsubnetCountCurrent::::insert(netuid, subsubnet_count); // Add some neurons let max_n = 16; @@ -2322,15 +2325,20 @@ fn test_trim_to_max_allowed_uids() { Rank::::insert(netuid, values.clone()); Trust::::insert(netuid, values.clone()); Consensus::::insert(netuid, values.clone()); - Incentive::::insert(netuid, values.clone()); Dividends::::insert(netuid, values.clone()); - LastUpdate::::insert(netuid, u64_values); PruningScores::::insert(netuid, values.clone()); ValidatorTrust::::insert(netuid, values.clone()); - StakeWeight::::insert(netuid, values); + StakeWeight::::insert(netuid, values.clone()); ValidatorPermit::::insert(netuid, bool_values.clone()); Active::::insert(netuid, bool_values); + for subid in 0..subsubnet_count.into() { + let netuid_index = + SubtensorModule::get_subsubnet_storage_index(netuid, SubId::from(subid)); + Incentive::::insert(netuid_index, values.clone()); + LastUpdate::::insert(netuid_index, u64_values.clone()); + } + // We set some owner immune uids let now = frame_system::Pallet::::block_number(); BlockAtRegistration::::set(netuid, 6, now); @@ -2359,8 +2367,12 @@ fn test_trim_to_max_allowed_uids() { } } - Weights::::insert(netuid, uid, weights); - Bonds::::insert(netuid, uid, bonds); + for subid in 0..subsubnet_count.into() { + let netuid_index = + SubtensorModule::get_subsubnet_storage_index(netuid, SubId::from(subid)); + Weights::::insert(netuid_index, uid, weights.clone()); + Bonds::::insert(netuid_index, uid, bonds.clone()); + } } // Normal case @@ -2397,20 +2409,29 @@ fn test_trim_to_max_allowed_uids() { assert_eq!(Trust::::get(netuid), expected_values); assert_eq!(Active::::get(netuid), expected_bools); assert_eq!(Consensus::::get(netuid), expected_values); - assert_eq!(Incentive::::get(netuid), expected_values); assert_eq!(Dividends::::get(netuid), expected_values); - assert_eq!(LastUpdate::::get(netuid), expected_u64_values); assert_eq!(PruningScores::::get(netuid), expected_values); assert_eq!(ValidatorTrust::::get(netuid), expected_values); assert_eq!(ValidatorPermit::::get(netuid), expected_bools); assert_eq!(StakeWeight::::get(netuid), expected_values); + for subid in 0..subsubnet_count.into() { + let netuid_index = + SubtensorModule::get_subsubnet_storage_index(netuid, SubId::from(subid)); + assert_eq!(Incentive::::get(netuid_index), expected_values); + assert_eq!(LastUpdate::::get(netuid_index), expected_u64_values); + } + // Ensure trimmed uids related storage has been cleared for uid in new_max_n..max_n { assert!(!Keys::::contains_key(netuid, uid)); assert!(!BlockAtRegistration::::contains_key(netuid, uid)); - assert!(!Weights::::contains_key(netuid, uid)); - assert!(!Bonds::::contains_key(netuid, uid)); + for subid in 0..subsubnet_count.into() { + let netuid_index = + SubtensorModule::get_subsubnet_storage_index(netuid, SubId::from(subid)); + assert!(!Weights::::contains_key(netuid_index, uid)); + assert!(!Bonds::::contains_key(netuid_index, uid)); + } } // Ensure trimmed uids hotkey related storage has been cleared @@ -2439,26 +2460,24 @@ fn test_trim_to_max_allowed_uids() { assert!(!Prometheus::::contains_key(netuid, hotkey)); } - // Ensure trimmed uids weights and bonds have been cleared - for uid in new_max_n..max_n { - assert!(!Weights::::contains_key(netuid, uid)); - assert!(!Bonds::::contains_key(netuid, uid)); - } - // Ensure trimmed uids weights and bonds connections have been trimmed correctly for uid in 0..new_max_n { - assert!( - Weights::::get(netuid, uid) - .iter() - .all(|(target_uid, _)| *target_uid < new_max_n), - "Found a weight with target_uid >= new_max_n" - ); - assert!( - Bonds::::get(netuid, uid) - .iter() - .all(|(target_uid, _)| *target_uid < new_max_n), - "Found a bond with target_uid >= new_max_n" - ); + for subid in 0..subsubnet_count.into() { + let netuid_index = + SubtensorModule::get_subsubnet_storage_index(netuid, SubId::from(subid)); + assert!( + Weights::::get(netuid_index, uid) + .iter() + .all(|(target_uid, _)| *target_uid < new_max_n), + "Found a weight with target_uid >= new_max_n" + ); + assert!( + Bonds::::get(netuid_index, uid) + .iter() + .all(|(target_uid, _)| *target_uid < new_max_n), + "Found a bond with target_uid >= new_max_n" + ); + } } // Actual number of neurons on the network updated after trimming diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index c910c17336..e01f17cad6 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -167,8 +167,7 @@ impl Pallet { immune_percentage < T::MaxImmuneUidsPercentage::get(), Error::::InvalidValue ); - - + // Get all emissions with their UIDs and sort by emission (descending) // This ensures we keep the highest emitters and remove the lowest ones let mut emissions = Emission::::get(netuid) From 45bf58df70cef64071888014921935c8c512b07a Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Thu, 11 Sep 2025 10:38:29 -0300 Subject: [PATCH 30/39] trigger ci From b54ca79581eb5d57af08011711fff0fed348e3f3 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Fri, 12 Sep 2025 13:13:14 -0300 Subject: [PATCH 31/39] fix get_immune_owner_hotkeys --- pallets/subtensor/src/subnets/registration.rs | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 6f11921dba..a620755f2e 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -389,6 +389,20 @@ impl Pallet { } fn get_immune_owner_hotkeys(netuid: NetUid, coldkey: &T::AccountId) -> Vec { + Self::get_immune_owner_tuples(netuid, coldkey) + .into_iter() + .map(|(_, hk)| hk) + .collect() + } + + pub fn get_immune_owner_uids(netuid: NetUid, coldkey: &T::AccountId) -> Vec { + Self::get_immune_owner_tuples(netuid, coldkey) + .into_iter() + .map(|(uid, _)| uid) + .collect() + } + + fn get_immune_owner_tuples(netuid: NetUid, coldkey: &T::AccountId) -> Vec<(u16, T::AccountId)> { // Gather (block, uid, hotkey) only for hotkeys that have a UID and a registration block. let mut triples: Vec<(u64, u16, T::AccountId)> = OwnedHotkeys::::get(coldkey) .into_iter() @@ -411,22 +425,24 @@ impl Pallet { triples.truncate(limit); } - // Project to just hotkeys - let mut immune_hotkeys: Vec = - triples.into_iter().map(|(_, _, hk)| hk).collect(); + // Project to uid/hotkey tuple + let mut immune_tuples: Vec<(u16, T::AccountId)> = + triples.into_iter().map(|(_, uid, hk)| (uid, hk)).collect(); // Insert subnet owner hotkey in the beginning of the list if valid and not // already present if let Ok(owner_hk) = SubnetOwnerHotkey::::try_get(netuid) { - if Uids::::get(netuid, &owner_hk).is_some() && !immune_hotkeys.contains(&owner_hk) { - immune_hotkeys.insert(0, owner_hk); - if immune_hotkeys.len() > limit { - immune_hotkeys.truncate(limit); + if let Some(owner_uid) = Uids::::get(netuid, &owner_hk) { + if !immune_tuples.contains(&(owner_uid, owner_hk.clone())) { + immune_tuples.insert(0, (owner_uid, owner_hk.clone())); + if immune_tuples.len() > limit { + immune_tuples.truncate(limit); + } } } } - immune_hotkeys + immune_tuples } /// Determine which peer to prune from the network by finding the element with the lowest pruning score out of From 2ce716bd26558b1a0d6fa14cc860d0b8f6e56d32 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 15 Sep 2025 13:27:21 -0300 Subject: [PATCH 32/39] MinAllowedUids to 64 --- 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 c81924398a..1c7a026380 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1152,7 +1152,7 @@ parameter_types! { pub const SubtensorInitialRAORecycledForRegistration: u64 = 0; // 0 rao pub const SubtensorInitialSenateRequiredStakePercentage: u64 = 1; // 1 percent of total stake pub const SubtensorInitialNetworkImmunity: u64 = 7 * 7200; - pub const SubtensorInitialMinAllowedUids: u16 = 256; + pub const SubtensorInitialMinAllowedUids: u16 = 64; pub const SubtensorInitialMinLockCost: u64 = 1_000_000_000_000; // 1000 TAO pub const SubtensorInitialSubnetOwnerCut: u16 = 11_796; // 18 percent // pub const SubtensorInitialSubnetLimit: u16 = 12; // (DEPRECATED) From a051137af5eee89fbf1270d6f66a818c813c07cd Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 15 Sep 2025 16:18:12 -0300 Subject: [PATCH 33/39] extract rate limit --- pallets/admin-utils/src/lib.rs | 4 ++-- pallets/subtensor/src/lib.rs | 5 +++++ pallets/subtensor/src/utils/rate_limiting.rs | 8 ++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index a3497de7bc..007f3d2b10 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1930,7 +1930,7 @@ pub mod pallet { let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( origin.clone(), netuid, - &[TransactionType::SetMaxAllowedUIDS], + &[TransactionType::SetMaxAllowedUids], )?; pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; @@ -1938,7 +1938,7 @@ pub mod pallet { pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, - &[TransactionType::SetMaxAllowedUIDS], + &[TransactionType::SetMaxAllowedUids], ); Ok(()) } diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 7780e1eb33..8af6f82724 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -618,6 +618,11 @@ pub mod pallet { T::InitialMaxAllowedUids::get() } #[pallet::type_value] + /// -- Rate limit for set max allowed UIDs + pub fn SetMaxAllowedUidsRateLimit() -> u64 { + prod_or_fast!(30 * 7200, 1) + } + #[pallet::type_value] /// Default immunity period. pub fn DefaultImmunityPeriod() -> u16 { T::InitialImmunityPeriod::get() diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index 2e1b890440..d8427c1ba4 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -14,7 +14,7 @@ pub enum TransactionType { OwnerHyperparamUpdate, SubsubnetCountUpdate, SubsubnetEmission, - SetMaxAllowedUIDS, + SetMaxAllowedUids, } /// Implement conversion from TransactionType to u16 @@ -30,7 +30,7 @@ impl From for u16 { TransactionType::OwnerHyperparamUpdate => 6, TransactionType::SubsubnetCountUpdate => 7, TransactionType::SubsubnetEmission => 8, - TransactionType::SetMaxAllowedUIDS => 9, + TransactionType::SetMaxAllowedUids => 9, } } } @@ -47,7 +47,7 @@ impl From for TransactionType { 6 => TransactionType::OwnerHyperparamUpdate, 7 => TransactionType::SubsubnetCountUpdate, 8 => TransactionType::SubsubnetEmission, - 9 => TransactionType::SetMaxAllowedUIDS, + 9 => TransactionType::SetMaxAllowedUids, _ => TransactionType::Unknown, } } @@ -65,7 +65,7 @@ impl Pallet { TransactionType::OwnerHyperparamUpdate => OwnerHyperparamRateLimit::::get(), TransactionType::SubsubnetCountUpdate => SubsubnetCountSetRateLimit::::get(), TransactionType::SubsubnetEmission => SubsubnetEmissionRateLimit::::get(), - TransactionType::SetMaxAllowedUIDS => 7200 * 30, + TransactionType::SetMaxAllowedUids => SetMaxAllowedUidsRateLimit::::get(), TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) _ => 0, } From ac5d6b3815a7731e204fa074301fdce1b67b77f4 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Mon, 15 Sep 2025 16:19:26 -0300 Subject: [PATCH 34/39] fix benchmarks --- pallets/admin-utils/src/benchmarking.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index c01a70d3b5..75c373372d 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -240,6 +240,7 @@ mod benchmarks { #[benchmark] fn sudo_set_min_allowed_uids() { + pallet_subtensor::Pallet::::set_admin_freeze_window(0); pallet_subtensor::Pallet::::init_new_network( 1u16.into(), /*netuid*/ 1u16, /*tempo*/ @@ -445,6 +446,7 @@ mod benchmarks { #[benchmark] fn sudo_trim_to_max_allowed_uids() { + pallet_subtensor::Pallet::::set_admin_freeze_window(0); pallet_subtensor::Pallet::::init_new_network( 1u16.into(), /*netuid*/ 1u16, /*sudo_tempo*/ From aa4a23e41049df57129ea8dd7e3bf55afaa5d534 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 16 Sep 2025 11:04:15 -0300 Subject: [PATCH 35/39] fix benchmarks --- pallets/admin-utils/src/benchmarking.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 513df4a908..8ee5ec996b 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -11,6 +11,8 @@ use frame_benchmarking::v1::account; use frame_benchmarking::v2::*; use frame_support::BoundedVec; use frame_system::RawOrigin; +use pallet_subtensor::SubnetworkN; +use subtensor_runtime_common::NetUid; use super::*; @@ -240,14 +242,18 @@ mod benchmarks { #[benchmark] fn sudo_set_min_allowed_uids() { + let netuid = NetUid::from(1); pallet_subtensor::Pallet::::set_admin_freeze_window(0); pallet_subtensor::Pallet::::init_new_network( - 1u16.into(), /*netuid*/ + netuid, 1u16, /*tempo*/ ); + + // Artificially set that some neurons are already registered + SubnetworkN::::set(netuid, 32); #[extrinsic_call] - _(RawOrigin::Root, 1u16.into()/*netuid*/, 32u16/*max_allowed_uids*/)/*sudo_set_max_allowed_uids*/; + _(RawOrigin::Root, netuid, 16u16/*min_allowed_uids*/)/*sudo_set_min_allowed_uids*/; } #[benchmark] @@ -453,7 +459,7 @@ mod benchmarks { ); #[extrinsic_call] - _(RawOrigin::Root, 1u16.into()/*netuid*/, 4097u16/*max_allowed_uids*/)/*sudo_trim_to_max_allowed_uids()*/; + _(RawOrigin::Root, 1u16.into()/*netuid*/, 256u16/*max_n*/)/*sudo_trim_to_max_allowed_uids()*/; } //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); From 5da86f1a3609693eb0a26070649983ce09d2e95f Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 16 Sep 2025 11:13:46 -0300 Subject: [PATCH 36/39] cargo fmt --- pallets/admin-utils/src/benchmarking.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 8ee5ec996b..67fba62b10 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -244,11 +244,8 @@ mod benchmarks { fn sudo_set_min_allowed_uids() { let netuid = NetUid::from(1); pallet_subtensor::Pallet::::set_admin_freeze_window(0); - pallet_subtensor::Pallet::::init_new_network( - netuid, - 1u16, /*tempo*/ - ); - + pallet_subtensor::Pallet::::init_new_network(netuid, 1u16 /*tempo*/); + // Artificially set that some neurons are already registered SubnetworkN::::set(netuid, 32); From 261ef5a1f09c5cf60c963bcd52d61fee43babbf1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Sep 2025 16:36:52 +0000 Subject: [PATCH 37/39] auto-update benchmark weights --- pallets/admin-utils/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 75ea020708..9c69a873c2 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1913,8 +1913,8 @@ pub mod pallet { /// the lowest emitters while preserving temporally and owner immune UIDs. The UIDs are /// then compressed to the left and storage is migrated to the new compressed UIDs. #[pallet::call_index(78)] - #[pallet::weight(Weight::from_parts(15_000_000, 0) - .saturating_add(::DbWeight::get().reads(1_u64)) + #[pallet::weight(Weight::from_parts(32_880_000, 0) + .saturating_add(::DbWeight::get().reads(6_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_trim_to_max_allowed_uids( origin: OriginFor, @@ -1940,8 +1940,8 @@ pub mod pallet { /// The extrinsic sets the minimum allowed UIDs for a subnet. /// It is only callable by the root account. #[pallet::call_index(79)] - #[pallet::weight(Weight::from_parts(18_800_000, 0) - .saturating_add(::DbWeight::get().reads(2_u64)) + #[pallet::weight(Weight::from_parts(24_370_000, 0) + .saturating_add(::DbWeight::get().reads(3_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_set_min_allowed_uids( origin: OriginFor, From f51659371a1a56fb0c68acab5f5d6861866f8665 Mon Sep 17 00:00:00 2001 From: Loris Moulin Date: Tue, 16 Sep 2025 18:48:53 -0300 Subject: [PATCH 38/39] fix rate limit --- pallets/admin-utils/src/lib.rs | 7 ++++--- pallets/subtensor/src/lib.rs | 2 +- pallets/subtensor/src/utils/rate_limiting.rs | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 9c69a873c2..89c83d68fc 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1924,7 +1924,7 @@ pub mod pallet { let maybe_owner = pallet_subtensor::Pallet::::ensure_sn_owner_or_root_with_limits( origin.clone(), netuid, - &[TransactionType::SetMaxAllowedUids], + &[TransactionType::MaxUidsTrimming], )?; pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; @@ -1932,7 +1932,7 @@ pub mod pallet { pallet_subtensor::Pallet::::record_owner_rl( maybe_owner, netuid, - &[TransactionType::SetMaxAllowedUids], + &[TransactionType::MaxUidsTrimming], ); Ok(()) } @@ -1948,7 +1948,8 @@ pub mod pallet { netuid: NetUid, min_allowed_uids: 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 diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 439129efc9..00fab25ae4 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -619,7 +619,7 @@ pub mod pallet { } #[pallet::type_value] /// -- Rate limit for set max allowed UIDs - pub fn SetMaxAllowedUidsRateLimit() -> u64 { + pub fn MaxUidsTrimmingRateLimit() -> u64 { prod_or_fast!(30 * 7200, 1) } #[pallet::type_value] diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index e9b33b3379..190634212a 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -15,7 +15,7 @@ pub enum TransactionType { OwnerHyperparamUpdate(Hyperparameter), SubsubnetCountUpdate, SubsubnetEmission, - SetMaxAllowedUids, + MaxUidsTrimming, } impl TransactionType { @@ -27,7 +27,7 @@ impl TransactionType { Self::RegisterNetwork => NetworkRateLimit::::get(), Self::SubsubnetCountUpdate => SubsubnetCountSetRateLimit::::get(), Self::SubsubnetEmission => SubsubnetEmissionRateLimit::::get(), - + Self::MaxUidsTrimming => MaxUidsTrimmingRateLimit::::get(), Self::Unknown => 0, // Default to no limit for unknown types (no limit) _ => 0, } @@ -140,7 +140,7 @@ impl From for u16 { TransactionType::OwnerHyperparamUpdate(_) => 6, TransactionType::SubsubnetCountUpdate => 7, TransactionType::SubsubnetEmission => 8, - TransactionType::SetMaxAllowedUids => 9, + TransactionType::MaxUidsTrimming => 9, } } } @@ -157,7 +157,7 @@ impl From for TransactionType { 6 => TransactionType::OwnerHyperparamUpdate(Hyperparameter::Unknown), 7 => TransactionType::SubsubnetCountUpdate, 8 => TransactionType::SubsubnetEmission, - 9 => TransactionType::SetMaxAllowedUids, + 9 => TransactionType::MaxUidsTrimming, _ => TransactionType::Unknown, } } From d79479545f3f9053464cc56d148ec82b8fe00581 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 17 Sep 2025 00:11:43 +0000 Subject: [PATCH 39/39] auto-update benchmark weights --- pallets/admin-utils/src/lib.rs | 4 ++-- pallets/subtensor/src/macros/dispatches.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 89c83d68fc..8b4f7204c3 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1940,8 +1940,8 @@ pub mod pallet { /// The extrinsic sets the minimum allowed UIDs for a subnet. /// It is only callable by the root account. #[pallet::call_index(79)] - #[pallet::weight(Weight::from_parts(24_370_000, 0) - .saturating_add(::DbWeight::get().reads(3_u64)) + #[pallet::weight(Weight::from_parts(31_550_000, 0) + .saturating_add(::DbWeight::get().reads(5_u64)) .saturating_add(::DbWeight::get().writes(1_u64)))] pub fn sudo_set_min_allowed_uids( origin: OriginFor, diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index a8b981048b..f4583c0711 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -882,7 +882,7 @@ mod dispatches { /// - Attempting to set prometheus information withing the rate limit min. /// #[pallet::call_index(40)] - #[pallet::weight((Weight::from_parts(41_240_000, 0) + #[pallet::weight((Weight::from_parts(32_510_000, 0) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)), DispatchClass::Normal, Pays::No))] pub fn serve_axon_tls(