From 7044287000ae68292152bb94fb501ed143272b82 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 28 Oct 2025 15:37:13 -0700 Subject: [PATCH 1/6] refactor neuron deregistration --- pallets/admin-utils/src/benchmarking.rs | 18 ++ pallets/admin-utils/src/lib.rs | 19 ++ pallets/admin-utils/src/tests/mod.rs | 36 ++- pallets/subtensor/src/lib.rs | 11 + pallets/subtensor/src/macros/events.rs | 3 + .../subtensor/src/migrations/migrate_rao.rs | 2 +- pallets/subtensor/src/subnets/registration.rs | 225 ++++++++++-------- pallets/subtensor/src/subnets/uids.rs | 20 +- pallets/subtensor/src/tests/mechanism.rs | 12 +- pallets/subtensor/src/tests/registration.rs | 203 ++++++++++------ pallets/subtensor/src/tests/uids.rs | 74 +++--- pallets/subtensor/src/utils/misc.rs | 10 + 12 files changed, 391 insertions(+), 242 deletions(-) diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 08589e530b..7b8124144d 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -627,5 +627,23 @@ mod benchmarks { _(RawOrigin::Root, 1u16.into()/*netuid*/, 256u16/*max_n*/)/*sudo_trim_to_max_allowed_uids()*/; } + #[benchmark] + fn sudo_set_min_non_immune_uids() { + // disable admin freeze window + pallet_subtensor::Pallet::::set_admin_freeze_window(0); + // create a network for netuid = 1 + pallet_subtensor::Pallet::::init_new_network( + 1u16.into(), /* netuid */ + 1u16, /* sudo_tempo */ + ); + + #[extrinsic_call] + _( + RawOrigin::Root, + 1u16.into(), /* netuid */ + 12u16, /* min */ + ); /* sudo_set_min_non_immune_uids() */ + } + //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 09c91d7a9d..f21958ba7e 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -2102,6 +2102,25 @@ pub mod pallet { ); Ok(()) } + + /// Sets the minimum number of non-immortal & non-immune UIDs that must remain in a subnet + #[pallet::call_index(81)] + #[pallet::weight(( + Weight::from_parts(7_114_000, 0) + .saturating_add(::DbWeight::get().writes(1)) + .saturating_add(::DbWeight::get().reads(0_u64)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_min_non_immune_uids( + origin: OriginFor, + netuid: NetUid, + min: u16, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_min_non_immune_uids(netuid, min); + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index fcf0e33ed2..f5822beaa2 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -2456,7 +2456,7 @@ fn test_trim_to_max_allowed_uids() { register_ok_neuron(netuid, U256::from(n), U256::from(n + i), 0); } - // Run some block to ensure stake weights are set and that we are past the immunity period + // Run some blocks 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()); @@ -2479,6 +2479,8 @@ fn test_trim_to_max_allowed_uids() { let u64_values: Vec = values.iter().map(|&v| v as u64).collect(); Emission::::set(netuid, alpha_values); + // NOTE: `Rank`, `Trust`, and `PruningScores` are *not* trimmed anymore, + // but we can still populate them without asserting on them. Rank::::insert(netuid, values.clone()); Trust::::insert(netuid, values.clone()); Consensus::::insert(netuid, values.clone()); @@ -2566,7 +2568,7 @@ fn test_trim_to_max_allowed_uids() { assert_eq!(MaxAllowedUids::::get(netuid), new_max_n); // Ensure the emission has been trimmed correctly, keeping the highest emitters - // and immune and compressed to the left + // (after respecting immunity/owner exclusions) and compressed to the left assert_eq!( Emission::::get(netuid), vec![ @@ -2580,16 +2582,16 @@ fn test_trim_to_max_allowed_uids() { 74.into() ] ); - // Ensure rest of storage has been trimmed correctly + + // Ensure rest of (active) 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); + + // NOTE: Rank/Trust/PruningScores are no longer trimmed; do not assert on them. assert_eq!(Active::::get(netuid), expected_bools); assert_eq!(Consensus::::get(netuid), expected_values); assert_eq!(Dividends::::get(netuid), expected_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); @@ -2670,7 +2672,7 @@ fn test_trim_to_max_allowed_uids() { assert_eq!(uid, Some(i)); } - // EVM association have been remapped correctly (uids: 7 -> 2, 14 -> 7) + // EVM association have been remapped correctly (uids: 6 -> 2, 14 -> 7) assert_eq!( AssociatedEvmAddress::::get(netuid, 2), Some((sp_core::H160::from_slice(b"12345678901234567891"), now)) @@ -2866,3 +2868,23 @@ fn test_sudo_set_min_allowed_uids() { ); }); } + +#[test] +fn test_sudo_set_min_non_immune_uids() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + add_network(netuid, 10); + + let to_be_set: u16 = 12; + let init_value: u16 = SubtensorModule::get_min_non_immune_uids(netuid); + + assert_ok!(AdminUtils::sudo_set_min_non_immune_uids( + <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + + assert!(init_value != to_be_set); + assert_eq!(SubtensorModule::get_min_non_immune_uids(netuid), to_be_set); + }); +} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 1f764b7baa..5c9b42106b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -898,6 +898,12 @@ pub mod pallet { 128 } + /// Default value for MinNonImmuneUids. + #[pallet::type_value] + pub fn DefaultMinNonImmuneUids() -> u16 { + 10u16 + } + #[pallet::storage] pub type MinActivityCutoff = StorageValue<_, u16, ValueQuery, DefaultMinActivityCutoff>; @@ -1878,6 +1884,11 @@ pub mod pallet { pub type NetworkRegistrationStartBlock = StorageValue<_, u64, ValueQuery, DefaultNetworkRegistrationStartBlock>; + #[pallet::storage] + /// --- MAP ( netuid ) --> minimum required number of non-immortal & non-immune UIDs + pub type MinNonImmuneUids = + StorageMap<_, Identity, NetUid, u16, ValueQuery, DefaultMinNonImmuneUids>; + /// ============================ /// ==== Subnet Mechanisms ===== /// ============================ diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index a784273f5a..9ed79bd16c 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -448,5 +448,8 @@ mod events { /// The account ID of the hotkey. hotkey: T::AccountId, }, + + /// The minimum allowed non-Immune UIDs has been set. + MinNonImmuneUidsSet(NetUid, u16), } } diff --git a/pallets/subtensor/src/migrations/migrate_rao.rs b/pallets/subtensor/src/migrations/migrate_rao.rs index e614827e90..25e220b6d4 100644 --- a/pallets/subtensor/src/migrations/migrate_rao.rs +++ b/pallets/subtensor/src/migrations/migrate_rao.rs @@ -113,7 +113,7 @@ pub fn migrate_rao() -> Weight { // Only register the owner coldkey if it's not already a hotkey on the subnet. if !Uids::::contains_key(*netuid, &owner_coldkey) { // Register the owner_coldkey as neuron to the network. - let _neuron_uid: u16 = Pallet::::register_neuron(*netuid, &owner_coldkey); + //let _neuron_uid: u16 = Pallet::::register_neuron(*netuid, &owner_coldkey); } // Register the neuron immediately. if !IdentitiesV2::::contains_key(owner_coldkey.clone()) { diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index b71aa68a0a..60f9be2bc6 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -1,4 +1,5 @@ use super::*; +use core::cmp::Ordering; use sp_core::{H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; use sp_runtime::Saturating; @@ -9,32 +10,29 @@ use system::pallet_prelude::BlockNumberFor; const LOG_TARGET: &str = "runtime::subtensor::registration"; impl Pallet { - pub fn register_neuron(netuid: NetUid, hotkey: &T::AccountId) -> u16 { - // Init param - let neuron_uid: u16; + pub fn register_neuron(netuid: NetUid, hotkey: &T::AccountId) -> Result { let block_number: u64 = Self::get_current_block_as_u64(); let current_subnetwork_n: u16 = Self::get_subnetwork_n(netuid); if current_subnetwork_n < Self::get_max_allowed_uids(netuid) { // No replacement required, the uid appends the subnetwork. - // We increment the subnetwork count here but not below. - neuron_uid = current_subnetwork_n; + let neuron_uid = current_subnetwork_n; // Expand subnetwork with new account. Self::append_neuron(netuid, hotkey, block_number); log::debug!("add new neuron account"); - } else { - // Replacement required. - // We take the neuron with the lowest pruning score here. - neuron_uid = Self::get_neuron_to_prune(netuid); - // Replace the neuron account with the new info. - Self::replace_neuron(netuid, neuron_uid, hotkey, block_number); - log::debug!("prune neuron"); + Ok(neuron_uid) + } else { + match Self::get_neuron_to_prune(netuid) { + Some(uid_to_replace) => { + Self::replace_neuron(netuid, uid_to_replace, hotkey, block_number); + log::debug!("prune neuron"); + Ok(uid_to_replace) + } + None => Err(Error::::NoNeuronIdAvailable.into()), + } } - - // Return the UID of the neuron. - neuron_uid } /// ---- The implementation for the extrinsic do_burned_registration: registering by burning TAO. @@ -93,14 +91,14 @@ impl Pallet { Error::::TooManyRegistrationsThisBlock ); - // --- 4. Ensure we are not exceeding the max allowed registrations per interval. + // --- 5. Ensure we are not exceeding the max allowed registrations per interval. ensure!( Self::get_registrations_this_interval(netuid) < Self::get_target_registrations_per_interval(netuid).saturating_mul(3), Error::::TooManyRegistrationsThisInterval ); - // --- 4. Ensure that the key is not already registered. + // --- 6. Ensure that the key is not already registered. ensure!( !Uids::::contains_key(netuid, &hotkey), Error::::HotKeyAlreadyRegisteredInSubNet @@ -128,7 +126,17 @@ impl Pallet { Error::::NoNeuronIdAvailable ); - // --- 10. Ensure the remove operation from the coldkey is a success. + // --- 10. If replacement is needed, ensure a safe prune candidate exists. + let current_n = Self::get_subnetwork_n(netuid); + let max_n = Self::get_max_allowed_uids(netuid); + if current_n >= max_n { + ensure!( + Self::get_neuron_to_prune(netuid).is_some(), + Error::::NoNeuronIdAvailable + ); + } + + // --- 11. Ensure the remove operation from the coldkey is a success. let actual_burn_amount = Self::remove_balance_from_coldkey_account(&coldkey, registration_cost.into())?; @@ -145,19 +153,19 @@ impl Pallet { }); // Actually perform the registration. - let neuron_uid: u16 = Self::register_neuron(netuid, &hotkey); + let neuron_uid: u16 = Self::register_neuron(netuid, &hotkey)?; - // --- 14. Record the registration and increment block and interval counters. + // --- 12. Record the registration and increment block and interval counters. BurnRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); Self::increase_rao_recycled(netuid, Self::get_burn(netuid).into()); - // --- 15. Deposit successful event. + // --- 13. Deposit successful event. log::debug!("NeuronRegistered( netuid:{netuid:?} uid:{neuron_uid:?} hotkey:{hotkey:?} ) "); Self::deposit_event(Event::NeuronRegistered(netuid, neuron_uid, hotkey)); - // --- 16. Ok and done. + // --- 14. Ok and done. Ok(()) } @@ -281,45 +289,49 @@ impl Pallet { Error::::InvalidDifficulty ); // Check that the work meets difficulty. - // --- 7. Check Work is the product of the nonce, the block number, and hotkey. Add this as used work. + // --- 9. Check Work is the product of the nonce, the block number, and hotkey. Add this as used work. let seal: H256 = Self::create_seal_hash(block_number, nonce, &hotkey); ensure!(seal == work_hash, Error::::InvalidSeal); UsedWork::::insert(work.clone(), current_block_number); - // DEPRECATED --- 8. Ensure that the key passes the registration requirement - // ensure!( - // Self::passes_network_connection_requirement(netuid, &hotkey), - // Error::::DidNotPassConnectedNetworkRequirement - // ); - - // --- 9. If the network account does not exist we will create it here. + // --- 10. If the network account does not exist we will create it here. Self::create_account_if_non_existent(&coldkey, &hotkey); - // --- 10. Ensure that the pairing is correct. + // --- 11. Ensure that the pairing is correct. ensure!( Self::coldkey_owns_hotkey(&coldkey, &hotkey), Error::::NonAssociatedColdKey ); - // Possibly there is no neuron slots at all. + // --- 12. Possibly there is no neuron slots at all. ensure!( Self::get_max_allowed_uids(netuid) != 0, Error::::NoNeuronIdAvailable ); + // --- 13. If replacement is needed, ensure a safe prune candidate exists. + let current_n = Self::get_subnetwork_n(netuid); + let max_n = Self::get_max_allowed_uids(netuid); + if current_n >= max_n { + ensure!( + Self::get_neuron_to_prune(netuid).is_some(), + Error::::NoNeuronIdAvailable + ); + } + // Actually perform the registration. - let neuron_uid: u16 = Self::register_neuron(netuid, &hotkey); + let neuron_uid: u16 = Self::register_neuron(netuid, &hotkey)?; - // --- 12. Record the registration and increment block and interval counters. + // --- 14. Record the registration and increment block and interval counters. POWRegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); RegistrationsThisInterval::::mutate(netuid, |val| val.saturating_inc()); RegistrationsThisBlock::::mutate(netuid, |val| val.saturating_inc()); - // --- 13. Deposit successful event. + // --- 15. Deposit successful event. log::debug!("NeuronRegistered( netuid:{netuid:?} uid:{neuron_uid:?} hotkey:{hotkey:?} ) "); Self::deposit_event(Event::NeuronRegistered(netuid, neuron_uid, hotkey)); - // --- 14. Ok and done. + // --- 16. Ok and done. Ok(()) } @@ -439,79 +451,102 @@ impl Pallet { immune_tuples } - /// Determine which peer to prune from the network by finding the element with the lowest pruning score out of - /// immunity period. If there is a tie for lowest pruning score, the neuron registered earliest is pruned. - /// If all neurons are in immunity period, the neuron with the lowest pruning score is pruned. If there is a tie for - /// the lowest pruning score, the immune neuron registered earliest is pruned. - /// Ties for earliest registration are broken by the neuron with the lowest uid. - pub fn get_neuron_to_prune(netuid: NetUid) -> u16 { - let mut min_score: u16 = u16::MAX; - let mut min_score_in_immunity: u16 = u16::MAX; - let mut earliest_registration: u64 = u64::MAX; - let mut earliest_registration_in_immunity: u64 = u64::MAX; - let mut uid_to_prune: u16 = 0; - let mut uid_to_prune_in_immunity: u16 = 0; - - // This boolean is used instead of checking if min_score == u16::MAX, to avoid the case - // where all non-immune neurons have pruning score u16::MAX - // This may be unlikely in practice. - let mut found_non_immune = false; - + /// Determine which neuron to prune. + /// + /// Sort candidates by: (is_immune, emission, registration_block, uid) **ascending**, + /// which effectively means: + /// 1) prefer pruning non-immune first (is_immune = false comes first), + /// 2) among them, prune the lowest emission first, + /// 3) tie-break by the earliest registration block, + /// 4) then by the lowest uid. + /// - Never prune "immortal" owner hotkeys (immune-owner set). + /// UIDs must remain **non-immortal & non-immune** after pruning. + /// If no candidate satisfies this, return `None`. + pub fn get_neuron_to_prune(netuid: NetUid) -> Option { let neurons_n = Self::get_subnetwork_n(netuid); if neurons_n == 0 { - return 0; // If there are no neurons in this network. + return None; } - // Get the list of immortal (top-k by registration time of owner owned) keys + // Owner-owned "immortal" hotkeys (never prune). let subnet_owner_coldkey = SubnetOwner::::get(netuid); let immortal_hotkeys = Self::get_immune_owner_hotkeys(netuid, &subnet_owner_coldkey); - for neuron_uid in 0..neurons_n { - // Do not deregister the owner's owned hotkeys - if let Ok(hotkey) = Self::get_hotkey_for_net_and_uid(netuid, neuron_uid) { - if immortal_hotkeys.contains(&hotkey) { - continue; + + // Snapshot emissions once to avoid repeated loads. + let emissions: Vec = Emission::::get(netuid); + + // Build candidate set: skip immortal hotkeys entirely. + // Each item is (is_immune, emission, reg_block, uid). + let mut candidates: Vec<(bool, AlphaCurrency, u64, u16)> = Vec::new(); + for uid in 0..neurons_n { + if let Ok(hk) = Self::get_hotkey_for_net_and_uid(netuid, uid) { + if immortal_hotkeys.contains(&hk) { + continue; // never prune owner-immortal hotkeys } + let is_immune = Self::get_neuron_is_immune(netuid, uid); + let emission = emissions + .get(uid as usize) + .cloned() + .unwrap_or_else(|| AlphaCurrency::ZERO); + let reg_block = Self::get_neuron_block_at_registration(netuid, uid); + candidates.push((is_immune, emission, reg_block, uid)); + } + } + + if candidates.is_empty() { + return None; + } + + // Safety floor for non-immortal & non-immune UIDs. + let min_free: u16 = Self::get_min_non_immune_uids(netuid); + + // Count current "free" (non-immortal AND non-immune) UIDs in candidates. + // (Immortals were already filtered out above.) + let free_count: u16 = candidates + .iter() + .filter(|(is_immune, _e, _b, _u)| !*is_immune) + .count() as u16; + + // Sort by (is_immune, emission, registration_block, uid), ascending. + candidates.sort_by(|a, b| { + // a = (is_immune_a, emission_a, block_a, uid_a) + // b = (is_immune_b, emission_b, block_b, uid_b) + // tuple-lexicographic order is fine if AlphaCurrency implements Ord + let ord_immune = a.0.cmp(&b.0); // false (non-immune) first + if ord_immune != Ordering::Equal { + return ord_immune; + } + let ord_emission = a.1.cmp(&b.1); // lowest emission first + if ord_emission != Ordering::Equal { + return ord_emission; + } + let ord_block = a.2.cmp(&b.2); // earliest registration first + if ord_block != Ordering::Equal { + return ord_block; } + a.3.cmp(&b.3) // lowest uid first + }); - let pruning_score: u16 = Self::get_pruning_score_for_uid(netuid, neuron_uid); - let block_at_registration: u64 = - Self::get_neuron_block_at_registration(netuid, neuron_uid); - let is_immune = Self::get_neuron_is_immune(netuid, neuron_uid); - - if is_immune { - // if the immune neuron has a lower pruning score than the minimum for immune neurons, - // or, if the pruning scores are equal and the immune neuron was registered earlier than the current minimum for immune neurons, - // then update the minimum pruning score and the uid to prune for immune neurons - if pruning_score < min_score_in_immunity - || (pruning_score == min_score_in_immunity - && block_at_registration < earliest_registration_in_immunity) - { - min_score_in_immunity = pruning_score; - earliest_registration_in_immunity = block_at_registration; - uid_to_prune_in_immunity = neuron_uid; + // Pick the best candidate that **does not** violate the min-free safety floor. + for (is_immune, _e, _b, uid) in candidates.iter().copied() { + if !is_immune { + // This is a non-immune candidate. We can remove it only if, + // after pruning, free_count - 1 >= min_free. + if free_count > min_free { + return Some(uid); + } else { + // Protected by the safety floor; try the next candidate. + continue; } } else { - found_non_immune = true; - // if the non-immune neuron has a lower pruning score than the minimum for non-immune neurons, - // or, if the pruning scores are equal and the non-immune neuron was registered earlier than the current minimum for non-immune neurons, - // then update the minimum pruning score and the uid to prune for non-immune neurons - if pruning_score < min_score - || (pruning_score == min_score && block_at_registration < earliest_registration) - { - min_score = pruning_score; - earliest_registration = block_at_registration; - uid_to_prune = neuron_uid; - } + // Immune candidate is allowed (owner-immortal were filtered out earlier), + // and removing it does not affect the "free" count. + return Some(uid); } } - if found_non_immune { - Self::set_pruning_score_for_uid(netuid, uid_to_prune, u16::MAX); - uid_to_prune - } else { - Self::set_pruning_score_for_uid(netuid, uid_to_prune_in_immunity, u16::MAX); - uid_to_prune_in_immunity - } + // No candidate can be pruned without violating the safety floor. + None } /// Determine whether the given hash satisfies the given difficulty. diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index 9d303cc979..45d0d6f681 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -18,12 +18,11 @@ impl Pallet { } } - /// Resets the trust, emission, consensus, incentive, dividends, bonds, and weights of + /// Resets the emission, consensus, incentives, dividends, bonds, and weights 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)); for mecid in 0..MechanismCountCurrent::::get(netuid).into() { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.into()); @@ -109,9 +108,7 @@ impl Pallet { // 2. Get and increase the uid count. SubnetworkN::::insert(netuid, next_uid.saturating_add(1)); - // 3. Expand Yuma Consensus with new position. - Rank::::mutate(netuid, |v| v.push(0)); - Trust::::mutate(netuid, |v| v.push(0)); + // 3. Expand per-neuron vectors with new position. Active::::mutate(netuid, |v| v.push(true)); Emission::::mutate(netuid, |v| v.push(0.into())); Consensus::::mutate(netuid, |v| v.push(0)); @@ -121,7 +118,6 @@ impl Pallet { Self::set_last_update_for_uid(netuid_index, next_uid, block_number); } Dividends::::mutate(netuid, |v| v.push(0)); - PruningScores::::mutate(netuid, |v| v.push(0)); ValidatorTrust::::mutate(netuid, |v| v.push(0)); ValidatorPermit::::mutate(netuid, |v| v.push(false)); @@ -233,12 +229,9 @@ impl Pallet { 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 dividends = Dividends::::get(netuid); - let pruning_scores = PruningScores::::get(netuid); let vtrust = ValidatorTrust::::get(netuid); let vpermit = ValidatorPermit::::get(netuid); let stake_weight = StakeWeight::::get(netuid); @@ -246,24 +239,18 @@ impl Pallet { // Create trimmed arrays by extracting values for kept uids only // Pre-allocate vectors with exact capacity for efficiency 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 &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()); @@ -271,12 +258,9 @@ impl Pallet { // 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); Dividends::::insert(netuid, trimmed_dividends); - PruningScores::::insert(netuid, trimmed_pruning_scores); ValidatorTrust::::insert(netuid, trimmed_vtrust); ValidatorPermit::::insert(netuid, trimmed_vpermit); StakeWeight::::insert(netuid, trimmed_stake_weight); diff --git a/pallets/subtensor/src/tests/mechanism.rs b/pallets/subtensor/src/tests/mechanism.rs index e5c46e8722..1d86e1c17d 100644 --- a/pallets/subtensor/src/tests/mechanism.rs +++ b/pallets/subtensor/src/tests/mechanism.rs @@ -855,7 +855,7 @@ fn neuron_dereg_cleans_weights_across_subids() { AlphaCurrency::from(3u64), ], ); - Trust::::insert(netuid, vec![11u16, 99u16, 33u16]); + Trust::::insert(netuid, vec![11u16, 99u16, 33u16]); // NOTE: trust is no longer cleared Consensus::::insert(netuid, vec![21u16, 88u16, 44u16]); Dividends::::insert(netuid, vec![7u16, 77u16, 17u16]); @@ -884,9 +884,7 @@ fn neuron_dereg_cleans_weights_across_subids() { assert_eq!(e[1], 0u64.into()); assert_eq!(e[2], 3u64.into()); - let t = Trust::::get(netuid); - assert_eq!(t, vec![11, 0, 33]); - + // Trust is no longer mutated by clear_neuron, so do not assert on it. let c = Consensus::::get(netuid); assert_eq!(c, vec![21, 0, 44]); @@ -921,7 +919,7 @@ fn clear_neuron_handles_absent_rows_gracefully() { // Minimal vectors with non-zero at index 0 (we will clear UID=0) Emission::::insert(netuid, vec![AlphaCurrency::from(5u64)]); - Trust::::insert(netuid, vec![5u16]); + Trust::::insert(netuid, vec![5u16]); // NOTE: trust is no longer cleared by clear_neuron Consensus::::insert(netuid, vec![6u16]); Dividends::::insert(netuid, vec![7u16]); @@ -929,12 +927,12 @@ fn clear_neuron_handles_absent_rows_gracefully() { let neuron_uid: u16 = 0; SubtensorModule::clear_neuron(netuid, neuron_uid); - // All zeroed at index 0 + // Emission/Consensus/Dividends zeroed at index 0 assert_eq!( Emission::::get(netuid), vec![AlphaCurrency::from(0u64)] ); - assert_eq!(Trust::::get(netuid), vec![0u16]); + // Do NOT assert on Trust; clear_neuron no longer mutates it. assert_eq!(Consensus::::get(netuid), vec![0u16]); assert_eq!(Dividends::::get(netuid), vec![0u16]); }); diff --git a/pallets/subtensor/src/tests/registration.rs b/pallets/subtensor/src/tests/registration.rs index 48e887d606..dba92ef24d 100644 --- a/pallets/subtensor/src/tests/registration.rs +++ b/pallets/subtensor/src/tests/registration.rs @@ -620,6 +620,9 @@ fn test_burn_registration_pruning_scenarios() { const IS_IMMUNE: bool = true; const NOT_IMMUNE: bool = false; + // --- Neutralize the safety floor for this test. + SubtensorModule::set_min_non_immune_uids(netuid, 0); + // Initial setup SubtensorModule::set_burn(netuid, burn_cost.into()); SubtensorModule::set_max_allowed_uids(netuid, max_allowed_uids); @@ -634,7 +637,7 @@ fn test_burn_registration_pruning_scenarios() { let mint_balance = burn_cost * max_allowed_uids as u64 + 1_000_000_000; SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, mint_balance); - // Register first half of neurons + // Register first half of neurons (uids: 0,1,2); all will be immune initially. for i in 0..3 { assert_ok!(SubtensorModule::burned_register( <::RuntimeOrigin>::signed(coldkey_account_id), @@ -644,51 +647,49 @@ fn test_burn_registration_pruning_scenarios() { step_block(1); } - // Note: pruning score is set to u16::MAX after getting neuron to prune - - // 1. Test if all immune neurons + // 1) All immune neurons assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), IS_IMMUNE); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), IS_IMMUNE); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), IS_IMMUNE); - SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); - SubtensorModule::set_pruning_score_for_uid(netuid, 1, 75); - SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50); - - // The immune neuron with the lowest score should be pruned - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 2); - - // 2. Test tie-breaking for immune neurons - SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); - SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50); + // Drive selection with emissions: lowest emission is pruned (among immune if all immune). + // Set: uid0=100, uid1=75, uid2=50 -> expect uid2 + Emission::::mutate(netuid, |v| { + v[0] = 100u64.into(); + v[1] = 75u64.into(); + v[2] = 50u64.into(); + }); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(2)); - // Should get the oldest neuron (i.e., neuron that was registered first) - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); + // 2) Tie-breaking for immune neurons: uid1=50, uid2=50 -> earliest registration among {1,2} is uid1 + Emission::::mutate(netuid, |v| { + v[1] = 50u64.into(); + v[2] = 50u64.into(); + }); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); - // 3. Test if no immune neurons + // 3) Make all three non-immune step_block(immunity_period); - - // ensure all neurons are non-immune assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), NOT_IMMUNE); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), NOT_IMMUNE); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), NOT_IMMUNE); - SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); - SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); - SubtensorModule::set_pruning_score_for_uid(netuid, 2, 75); - - // The non-immune neuron with the lowest score should be pruned - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); - - // 4. Test tie-breaking for non-immune neurons - SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); - SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50); + // Among non-immune, choose lowest emission: set uid0=100, uid1=50, uid2=75 -> expect uid1 + Emission::::mutate(netuid, |v| { + v[0] = 100u64.into(); + v[1] = 50u64.into(); + v[2] = 75u64.into(); + }); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); - // Should get the oldest non-immune neuron - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); + // 4) Non-immune tie-breaking: uid1=50, uid2=50 -> earliest registration among {1,2} is uid1 + Emission::::mutate(netuid, |v| { + v[1] = 50u64.into(); + v[2] = 50u64.into(); + }); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); - // 5. Test mixed immunity - // Register second batch of neurons (these will be non-immune) + // 5) Mixed immunity: register another 3 immune neurons (uids: 3,4,5) for i in 3..6 { assert_ok!(SubtensorModule::burned_register( <::RuntimeOrigin>::signed(coldkey_account_id), @@ -698,31 +699,37 @@ fn test_burn_registration_pruning_scenarios() { step_block(1); } - // Ensure all new neurons are immune + // Ensure new neurons are immune assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 3), IS_IMMUNE); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 4), IS_IMMUNE); assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 5), IS_IMMUNE); - // Set pruning scores for all neurons - SubtensorModule::set_pruning_score_for_uid(netuid, 0, 75); // non-immune - SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); // non-immune - SubtensorModule::set_pruning_score_for_uid(netuid, 2, 60); // non-immune - SubtensorModule::set_pruning_score_for_uid(netuid, 3, 40); // immune - SubtensorModule::set_pruning_score_for_uid(netuid, 4, 55); // immune - SubtensorModule::set_pruning_score_for_uid(netuid, 5, 45); // immune - - // The non-immune neuron with the lowest score should be pruned - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1); - - // If we remove the lowest non-immune neuron, it should choose the next lowest non-immune - SubtensorModule::set_pruning_score_for_uid(netuid, 1, u16::MAX); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 2); - - // If we make all non-immune neurons have high scores, it should choose the oldest non-immune neuron - SubtensorModule::set_pruning_score_for_uid(netuid, 0, u16::MAX); - SubtensorModule::set_pruning_score_for_uid(netuid, 1, u16::MAX); - SubtensorModule::set_pruning_score_for_uid(netuid, 2, u16::MAX); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 0); + // Set emissions: + // non-immune (0..2): [75, 50, 60] -> lowest among non-immune is uid1 + // immune (3..5): [40, 55, 45] -> ignored while a non-immune exists + Emission::::mutate(netuid, |v| { + v[0] = 75u64.into(); + v[1] = 50u64.into(); + v[2] = 60u64.into(); + v[3] = 40u64.into(); + v[4] = 55u64.into(); + v[5] = 45u64.into(); + }); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(1)); + + // Remove lowest non-immune by making uid1 emission very high -> next lowest non-immune is uid2 + Emission::::mutate(netuid, |v| { + v[1] = 10_000u64.into(); + }); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(2)); + + // If all non-immune are equally high, choose the oldest non-immune -> uid0 + Emission::::mutate(netuid, |v| { + v[0] = 10_000u64.into(); + v[1] = 10_000u64.into(); + v[2] = 10_000u64.into(); + }); + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(0)); }); } @@ -1292,21 +1299,31 @@ fn test_registration_get_uid_to_prune_all_in_immunity_period() { System::set_block_number(0); let netuid = NetUid::from(1); add_network(netuid, 1, 0); - log::info!("add network"); + + // Neutralize safety floor and owner-immortality for deterministic selection + SubtensorModule::set_min_non_immune_uids(netuid, 0); + ImmuneOwnerUidsLimit::::insert(netuid, 0); + // Make sure the subnet owner is not one of the test coldkeys + SubnetOwner::::insert(netuid, U256::from(999_999u64)); + register_ok_neuron(netuid, U256::from(0), U256::from(0), 39420842); register_ok_neuron(netuid, U256::from(1), U256::from(1), 12412392); - SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); - SubtensorModule::set_pruning_score_for_uid(netuid, 1, 110); + SubtensorModule::set_immunity_period(netuid, 2); - assert_eq!(SubtensorModule::get_pruning_score_for_uid(netuid, 0), 100); - assert_eq!(SubtensorModule::get_pruning_score_for_uid(netuid, 1), 110); assert_eq!(SubtensorModule::get_immunity_period(netuid), 2); assert_eq!(SubtensorModule::get_current_block_as_u64(), 0); assert_eq!( SubtensorModule::get_neuron_block_at_registration(netuid, 0), 0 ); - assert_eq!(SubtensorModule::get_neuron_to_prune(NetUid::ROOT), 0); + + // Both immune; prune lowest emission among immune (uid0=100, uid1=110 => uid0) + Emission::::mutate(netuid, |v| { + v[0] = 100u64.into(); + v[1] = 110u64.into(); + }); + + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(0)); }); } @@ -1316,23 +1333,34 @@ fn test_registration_get_uid_to_prune_none_in_immunity_period() { System::set_block_number(0); let netuid = NetUid::from(1); add_network(netuid, 1, 0); - log::info!("add network"); + + // Neutralize safety floor and owner-immortality for deterministic selection + SubtensorModule::set_min_non_immune_uids(netuid, 0); + ImmuneOwnerUidsLimit::::insert(netuid, 0); + SubnetOwner::::insert(netuid, U256::from(999_999u64)); + register_ok_neuron(netuid, U256::from(0), U256::from(0), 39420842); register_ok_neuron(netuid, U256::from(1), U256::from(1), 12412392); - SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100); - SubtensorModule::set_pruning_score_for_uid(netuid, 1, 110); + SubtensorModule::set_immunity_period(netuid, 2); - assert_eq!(SubtensorModule::get_pruning_score_for_uid(netuid, 0), 100); - assert_eq!(SubtensorModule::get_pruning_score_for_uid(netuid, 1), 110); assert_eq!(SubtensorModule::get_immunity_period(netuid), 2); assert_eq!(SubtensorModule::get_current_block_as_u64(), 0); assert_eq!( SubtensorModule::get_neuron_block_at_registration(netuid, 0), 0 ); + + // Advance beyond immunity -> both non-immune step_block(3); assert_eq!(SubtensorModule::get_current_block_as_u64(), 3); - assert_eq!(SubtensorModule::get_neuron_to_prune(NetUid::ROOT), 0); + + // Among non-immune, lowest emission pruned: uid0=100, uid1=110 -> expect uid0 + Emission::::mutate(netuid, |v| { + v[0] = 100u64.into(); + v[1] = 110u64.into(); + }); + + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(0)); }); } @@ -1341,11 +1369,9 @@ fn test_registration_get_uid_to_prune_none_in_immunity_period() { fn test_registration_get_uid_to_prune_owner_immortality() { new_test_ext(1).execute_with(|| { [ - // Burn key limit to 1 - testing the limits - // Other owner's hotkey is pruned because there's only 1 immune key and - // pruning score of owner key is lower + // Limit = 1: only the earliest owner hotkey is immortal -> prune the other owner hotkey (uid 1) (1, 1), - // Burn key limit to 2 - both owner keys are immune + // Limit = 2: both owner hotkeys are immortal -> prune the non-owner (uid 2) (2, 2), ] .iter() @@ -1362,6 +1388,7 @@ fn test_registration_get_uid_to_prune_owner_immortality() { let non_owner_hk = U256::from(3); let netuid = add_dynamic_network(&subnet_owner_hk, &subnet_owner_ck); + // Make sure registration blocks are set BlockAtRegistration::::insert(netuid, 1, 1); BlockAtRegistration::::insert(netuid, 2, 2); Uids::::insert(netuid, other_owner_hk, 1); @@ -1371,14 +1398,25 @@ fn test_registration_get_uid_to_prune_owner_immortality() { ImmunityPeriod::::insert(netuid, 1); SubnetworkN::::insert(netuid, 3); - step_block(10); + // Neutralize safety floor for this test + SubtensorModule::set_min_non_immune_uids(netuid, 0); + + step_block(10); // all non-immune + // Configure the number of immortal owner UIDs ImmuneOwnerUidsLimit::::insert(netuid, *limit); - // Set lower pruning score to sn owner keys - PruningScores::::insert(netuid, vec![0, 0, 1]); + // Drive selection by emissions (lowest first) + // uid0=0, uid1=0, uid2=1 + Emission::::insert( + netuid, + vec![AlphaCurrency::from(0), 0u64.into(), 1u64.into()], + ); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), *uid_to_prune); + assert_eq!( + SubtensorModule::get_neuron_to_prune(netuid), + Some(*uid_to_prune) + ); }); }); } @@ -1411,14 +1449,23 @@ fn test_registration_get_uid_to_prune_owner_immortality_all_immune() { ImmunityPeriod::::insert(netuid, 100); SubnetworkN::::insert(netuid, 3); - step_block(20); + // Neutralize safety floor for this test + SubtensorModule::set_min_non_immune_uids(netuid, 0); + + step_block(20); // all still immune ImmuneOwnerUidsLimit::::insert(netuid, limit); - // Set lower pruning score to sn owner keys - PruningScores::::insert(netuid, vec![0, 0, 1]); + // Lowest emission among non-immortal candidates -> uid2 + Emission::::insert( + netuid, + vec![AlphaCurrency::from(0), 0u64.into(), 1u64.into()], + ); - assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), uid_to_prune); + assert_eq!( + SubtensorModule::get_neuron_to_prune(netuid), + Some(uid_to_prune) + ); }); } diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index 8fee5f7507..0ab6da3d30 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -462,6 +462,12 @@ fn test_get_neuron_to_prune_owner_not_pruned() { SubtensorModule::set_target_registrations_per_interval(netuid, 100); SubnetOwner::::insert(netuid, owner_coldkey); + // Ensure owner's hotkey is counted as immortal in this test + ImmuneOwnerUidsLimit::::insert(netuid, 1); + + // Neutralize safety floor for this test + SubtensorModule::set_min_non_immune_uids(netuid, 0); + let owner_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &owner_hotkey) .expect("Owner neuron should already be registered by add_dynamic_network"); @@ -479,30 +485,20 @@ fn test_get_neuron_to_prune_owner_not_pruned() { let uid_2 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &additional_hotkey_2) .expect("Should be registered"); - SubtensorModule::set_pruning_score_for_uid(netuid, owner_uid, 0); - SubtensorModule::set_pruning_score_for_uid(netuid, uid_1, 1); - SubtensorModule::set_pruning_score_for_uid(netuid, uid_2, 2); + // Set emissions; owner has the lowest but is immortal, so choose uid_1 + Emission::::mutate(netuid, |v| { + v[owner_uid as usize] = 0u64.into(); + v[uid_1 as usize] = 1u64.into(); + v[uid_2 as usize] = 2u64.into(); + }); let pruned_uid = SubtensorModule::get_neuron_to_prune(netuid); - // - The pruned UID must be `uid_1` (score=1). - // - The owner's UID remains unpruned. + // Expect to prune uid_1; owner's UID is skipped as immortal. assert_eq!( - pruned_uid, uid_1, - "Should prune the neuron with pruning score=1, not the owner (score=0)." - ); - - let pruned_score = SubtensorModule::get_pruning_score_for_uid(netuid, uid_1); - assert_eq!( - pruned_score, - u16::MAX, - "Pruned neuron's score should be set to u16::MAX" - ); - - let owner_score = SubtensorModule::get_pruning_score_for_uid(netuid, owner_uid); - assert_eq!( - owner_score, 0, - "Owner's pruning score remains 0, indicating it was skipped" + pruned_uid, + Some(uid_1), + "Should prune the neuron with the lowest emission among non-immortal candidates." ); }); } @@ -520,10 +516,16 @@ fn test_get_neuron_to_prune_owner_pruned_if_not_in_sn_owner_hotkey_map() { SubtensorModule::set_target_registrations_per_interval(netuid, 100); SubnetOwner::::insert(netuid, owner_coldkey); + // Make only one owner hotkey immortal at a time + ImmuneOwnerUidsLimit::::insert(netuid, 1); + + // Neutralize safety floor for this test + SubtensorModule::set_min_non_immune_uids(netuid, 0); + let owner_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &owner_hotkey) .expect("Owner neuron should already be registered by add_dynamic_network"); - // Register another hotkey for the owner + // Register another hotkey for the owner (same coldkey) register_ok_neuron(netuid, other_owner_hotkey, owner_coldkey, 0); let other_owner_uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &other_owner_hotkey) @@ -543,28 +545,28 @@ fn test_get_neuron_to_prune_owner_pruned_if_not_in_sn_owner_hotkey_map() { let uid_3 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &additional_hotkey_2) .expect("Should be registered"); - SubtensorModule::set_pruning_score_for_uid(netuid, owner_uid, 0); - // Other owner key has pruning score not worse than the owner's first hotkey, but worse than the additional hotkeys - SubtensorModule::set_pruning_score_for_uid(netuid, other_owner_uid, 1); - SubtensorModule::set_pruning_score_for_uid(netuid, uid_2, 2); - SubtensorModule::set_pruning_score_for_uid(netuid, uid_3, 3); + // Case 1: With ImmuneOwnerUidsLimit = 1, the default SubnetOwnerHotkey is `owner_hotkey`. + // That makes `owner_uid` immortal and leaves `other_owner_uid` pruneable. + // Emissions: owner=0 (immortal), other_owner=1, uid_2=2, uid_3=3 -> expect other_owner_uid + Emission::::mutate(netuid, |v| { + v[owner_uid as usize] = 0u64.into(); + v[other_owner_uid as usize] = 1u64.into(); + v[uid_2 as usize] = 2u64.into(); + v[uid_3 as usize] = 3u64.into(); + }); let pruned_uid = SubtensorModule::get_neuron_to_prune(netuid); - assert_eq!(pruned_uid, other_owner_uid, "Should prune the owner"); + assert_eq!(pruned_uid, Some(other_owner_uid), "Should prune the owner"); - // Set the owner's other hotkey as the SubnetOwnerHotkey + // Case 2: Make the other owner's hotkey the SubnetOwnerHotkey, so it becomes the prioritized + // immortal one; now `owner_uid` is not immortal and has the lowest emission -> prune it. SubnetOwnerHotkey::::insert(netuid, other_owner_hotkey); - // Reset pruning scores - SubtensorModule::set_pruning_score_for_uid(netuid, owner_uid, 0); - SubtensorModule::set_pruning_score_for_uid(netuid, other_owner_uid, 1); - SubtensorModule::set_pruning_score_for_uid(netuid, uid_2, 2); - SubtensorModule::set_pruning_score_for_uid(netuid, uid_3, 3); - + // Emissions remain the same; `owner_uid` now becomes the lowest non-immortal candidate. let pruned_uid = SubtensorModule::get_neuron_to_prune(netuid); - assert_eq!( - pruned_uid, owner_uid, + pruned_uid, + Some(owner_uid), "Should prune the owner, not the top-stake owner hotkey and not the additional hotkeys" ); }); diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index ee16b9aa01..cb01dbe2dc 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -364,6 +364,16 @@ impl Pallet { pub fn get_neuron_block_at_registration(netuid: NetUid, neuron_uid: u16) -> u64 { BlockAtRegistration::::get(netuid, neuron_uid) } + /// Returns the minimum number of non-immortal & non-immune UIDs that must remain in a subnet. + pub fn get_min_non_immune_uids(netuid: NetUid) -> u16 { + MinNonImmuneUids::::get(netuid) + } + + /// Sets the minimum number of non-immortal & non-immune UIDs that must remain in a subnet. + pub fn set_min_non_immune_uids(netuid: NetUid, min: u16) { + MinNonImmuneUids::::insert(netuid, min); + Self::deposit_event(Event::MinNonImmuneUidsSet(netuid, min)); + } // ======================== // ===== Take checks ====== From 8d7a74620c550dde393d0cea29d56200c106d4d7 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:32:34 -0700 Subject: [PATCH 2/6] clippy --- pallets/subtensor/src/subnets/registration.rs | 2 +- pallets/subtensor/src/tests/registration.rs | 3 +++ pallets/subtensor/src/tests/uids.rs | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 60f9be2bc6..e3a086ff15 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -487,7 +487,7 @@ impl Pallet { let emission = emissions .get(uid as usize) .cloned() - .unwrap_or_else(|| AlphaCurrency::ZERO); + .unwrap_or(AlphaCurrency::ZERO); let reg_block = Self::get_neuron_block_at_registration(netuid, uid); candidates.push((is_immune, emission, reg_block, uid)); } diff --git a/pallets/subtensor/src/tests/registration.rs b/pallets/subtensor/src/tests/registration.rs index dba92ef24d..d4782917a2 100644 --- a/pallets/subtensor/src/tests/registration.rs +++ b/pallets/subtensor/src/tests/registration.rs @@ -607,6 +607,7 @@ fn test_burn_adjustment() { }); } +#[allow(clippy::indexing_slicing)] #[test] fn test_burn_registration_pruning_scenarios() { new_test_ext(1).execute_with(|| { @@ -1293,6 +1294,7 @@ fn test_registration_failed_no_signature() { }); } +#[allow(clippy::indexing_slicing)] #[test] fn test_registration_get_uid_to_prune_all_in_immunity_period() { new_test_ext(1).execute_with(|| { @@ -1327,6 +1329,7 @@ fn test_registration_get_uid_to_prune_all_in_immunity_period() { }); } +#[allow(clippy::indexing_slicing)] #[test] fn test_registration_get_uid_to_prune_none_in_immunity_period() { new_test_ext(1).execute_with(|| { diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index 0ab6da3d30..ecee4c7bcb 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -450,6 +450,7 @@ fn test_replace_neuron_subnet_owner_not_replaced_if_in_sn_owner_hotkey_map() { }); } +#[allow(clippy::indexing_slicing)] #[test] fn test_get_neuron_to_prune_owner_not_pruned() { new_test_ext(1).execute_with(|| { @@ -503,6 +504,7 @@ fn test_get_neuron_to_prune_owner_not_pruned() { }); } +#[allow(clippy::indexing_slicing)] #[test] fn test_get_neuron_to_prune_owner_pruned_if_not_in_sn_owner_hotkey_map() { new_test_ext(1).execute_with(|| { From 8fb5c36888b267c1e7900a7fb05c57dc819ebc1a Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:55:12 -0700 Subject: [PATCH 3/6] remove trust, rank, pruning_score from epoch --- pallets/subtensor/src/epoch/run_epoch.rs | 59 ---------------------- pallets/subtensor/src/subnets/mechanism.rs | 16 ------ pallets/subtensor/src/tests/epoch.rs | 21 +++----- pallets/subtensor/src/tests/epoch_logs.rs | 14 ----- pallets/subtensor/src/tests/mechanism.rs | 15 +----- 5 files changed, 9 insertions(+), 116 deletions(-) diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index 5e4dd1f43e..763df09c4f 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -18,10 +18,7 @@ pub struct EpochTerms { pub stake_weight: u16, pub active: bool, pub emission: AlphaCurrency, - pub rank: u16, - pub trust: u16, pub consensus: u16, - pub pruning_score: u16, pub validator_trust: u16, pub new_validator_permit: bool, pub bond: Vec<(u16, u16)>, @@ -128,22 +125,16 @@ impl Pallet { let active = extract_from_sorted_terms!(terms_sorted, active); let emission = extract_from_sorted_terms!(terms_sorted, emission); - let rank = extract_from_sorted_terms!(terms_sorted, rank); - let trust = extract_from_sorted_terms!(terms_sorted, trust); let consensus = extract_from_sorted_terms!(terms_sorted, consensus); let dividend = extract_from_sorted_terms!(terms_sorted, dividend); - let pruning_score = extract_from_sorted_terms!(terms_sorted, pruning_score); let validator_trust = extract_from_sorted_terms!(terms_sorted, validator_trust); let new_validator_permit = extract_from_sorted_terms!(terms_sorted, new_validator_permit); let stake_weight = extract_from_sorted_terms!(terms_sorted, stake_weight); Active::::insert(netuid, active.clone()); Emission::::insert(netuid, emission); - Rank::::insert(netuid, rank); - Trust::::insert(netuid, trust); Consensus::::insert(netuid, consensus); Dividends::::insert(netuid, dividend); - PruningScores::::insert(netuid, pruning_score); ValidatorTrust::::insert(netuid, validator_trust); ValidatorPermit::::insert(netuid, new_validator_permit); StakeWeight::::insert(netuid, stake_weight); @@ -325,9 +316,6 @@ impl Pallet { // == Consensus, Validator Trust == // ================================ - // Compute preranks: r_j = SUM(i) w_ij * s_i - let preranks: Vec = matmul(&weights, &active_stake); - // Consensus majority ratio, e.g. 51%. let kappa: I32F32 = Self::get_float_kappa(netuid); // Calculate consensus as stake-weighted median of weights. @@ -345,9 +333,6 @@ impl Pallet { // Compute ranks: r_j = SUM(i) w_ij * s_i let mut ranks: Vec = matmul(&clipped_weights, &active_stake); - // Compute server trust: ratio of rank after vs. rank before. - let trust: Vec = vecdiv(&ranks, &preranks); - inplace_normalize(&mut ranks); let incentive: Vec = ranks.clone(); log::trace!("I: {:?}", &incentive); @@ -494,10 +479,6 @@ impl Pallet { log::trace!("nCE: {:?}", &normalized_combined_emission); log::trace!("CE: {:?}", &combined_emission); - // Set pruning scores using combined emission scores. - let pruning_scores: Vec = normalized_combined_emission.clone(); - log::trace!("P: {:?}", &pruning_scores); - // =================== // == Value storage == // =================== @@ -506,14 +487,6 @@ impl Pallet { .iter() .map(|xi| fixed_proportion_to_u16(*xi)) .collect::>(); - let cloned_ranks: Vec = ranks - .iter() - .map(|xi| fixed_proportion_to_u16(*xi)) - .collect::>(); - let cloned_trust: Vec = trust - .iter() - .map(|xi| fixed_proportion_to_u16(*xi)) - .collect::>(); let cloned_consensus: Vec = consensus .iter() .map(|xi| fixed_proportion_to_u16(*xi)) @@ -526,7 +499,6 @@ impl Pallet { .iter() .map(|xi| fixed_proportion_to_u16(*xi)) .collect::>(); - let cloned_pruning_scores: Vec = vec_max_upscale_to_u16(&pruning_scores); let cloned_validator_trust: Vec = validator_trust .iter() .map(|xi| fixed_proportion_to_u16(*xi)) @@ -534,12 +506,9 @@ impl Pallet { StakeWeight::::insert(netuid, cloned_stake_weight.clone()); Active::::insert(netuid, active.clone()); Emission::::insert(netuid, cloned_emission); - Rank::::insert(netuid, cloned_ranks); - Trust::::insert(netuid, cloned_trust); Consensus::::insert(netuid, cloned_consensus); Incentive::::insert(NetUidStorageIndex::from(netuid), cloned_incentive); Dividends::::insert(netuid, cloned_dividends); - PruningScores::::insert(netuid, cloned_pruning_scores); ValidatorTrust::::insert(netuid, cloned_validator_trust); ValidatorPermit::::insert(netuid, new_validator_permits.clone()); @@ -790,10 +759,6 @@ impl Pallet { // == Consensus, Validator Trust == // ================================ - // Compute preranks: r_j = SUM(i) w_ij * s_i - let preranks: Vec = matmul_sparse(&weights, &active_stake, n); - log::trace!("Ranks (before): {:?}", &preranks); - // Consensus majority ratio, e.g. 51%. let kappa: I32F32 = Self::get_float_kappa(netuid); // Calculate consensus as stake-weighted median of weights. @@ -814,11 +779,6 @@ impl Pallet { // Compute ranks: r_j = SUM(i) w_ij * s_i. let mut ranks: Vec = matmul_sparse(&clipped_weights, &active_stake, n); - log::trace!("Ranks (after): {:?}", &ranks); - - // Compute server trust: ratio of rank after vs. rank before. - let trust: Vec = vecdiv(&ranks, &preranks); // range: I32F32(0, 1) - log::trace!("Trust: {:?}", &trust); inplace_normalize(&mut ranks); // range: I32F32(0, 1) let incentive: Vec = ranks.clone(); @@ -1004,10 +964,6 @@ impl Pallet { ); log::trace!("Combined Emission: {:?}", &combined_emission); - // Set pruning scores using combined emission scores. - let pruning_scores: Vec = normalized_combined_emission.clone(); - log::trace!("Pruning Scores: {:?}", &pruning_scores); - // =========================== // == Populate epoch output == // =========================== @@ -1016,14 +972,6 @@ impl Pallet { .map(|xi| fixed_proportion_to_u16(*xi)) .collect::>(); let cloned_emission = combined_emission.clone(); - let cloned_ranks: Vec = ranks - .iter() - .map(|xi| fixed_proportion_to_u16(*xi)) - .collect::>(); - let cloned_trust: Vec = trust - .iter() - .map(|xi| fixed_proportion_to_u16(*xi)) - .collect::>(); let cloned_consensus: Vec = consensus .iter() .map(|xi| fixed_proportion_to_u16(*xi)) @@ -1036,7 +984,6 @@ impl Pallet { .iter() .map(|xi| fixed_proportion_to_u16(*xi)) .collect::>(); - let cloned_pruning_scores: Vec = vec_max_upscale_to_u16(&pruning_scores); let cloned_validator_trust: Vec = validator_trust .iter() .map(|xi| fixed_proportion_to_u16(*xi)) @@ -1056,13 +1003,7 @@ impl Pallet { .unwrap_or_default(); terms.active = active.get(terms.uid).copied().unwrap_or_default(); terms.emission = cloned_emission.get(terms.uid).copied().unwrap_or_default(); - terms.rank = cloned_ranks.get(terms.uid).copied().unwrap_or_default(); - terms.trust = cloned_trust.get(terms.uid).copied().unwrap_or_default(); terms.consensus = cloned_consensus.get(terms.uid).copied().unwrap_or_default(); - terms.pruning_score = cloned_pruning_scores - .get(terms.uid) - .copied() - .unwrap_or_default(); terms.validator_trust = cloned_validator_trust .get(terms.uid) .copied() diff --git a/pallets/subtensor/src/subnets/mechanism.rs b/pallets/subtensor/src/subnets/mechanism.rs index 6598c308f2..481974ef05 100644 --- a/pallets/subtensor/src/subnets/mechanism.rs +++ b/pallets/subtensor/src/subnets/mechanism.rs @@ -311,20 +311,11 @@ impl Pallet { terms.emission, sub_weight, ); - acc_terms.rank = - Self::weighted_acc_u16(acc_terms.rank, terms.rank, sub_weight); - acc_terms.trust = - Self::weighted_acc_u16(acc_terms.trust, terms.trust, sub_weight); acc_terms.consensus = Self::weighted_acc_u16( acc_terms.consensus, terms.consensus, sub_weight, ); - acc_terms.pruning_score = Self::weighted_acc_u16( - acc_terms.pruning_score, - terms.pruning_score, - sub_weight, - ); acc_terms.validator_trust = Self::weighted_acc_u16( acc_terms.validator_trust, terms.validator_trust, @@ -351,14 +342,7 @@ impl Pallet { terms.emission, sub_weight, ), - rank: Self::weighted_acc_u16(0, terms.rank, sub_weight), - trust: Self::weighted_acc_u16(0, terms.trust, sub_weight), consensus: Self::weighted_acc_u16(0, terms.consensus, sub_weight), - pruning_score: Self::weighted_acc_u16( - 0, - terms.pruning_score, - sub_weight, - ), validator_trust: Self::weighted_acc_u16( 0, terms.validator_trust, diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index cdf44df645..32f754f78d 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -718,39 +718,34 @@ fn test_512_graph() { SubtensorModule::get_total_stake_for_hotkey(&(U256::from(uid))), max_stake_per_validator.into() ); - assert_eq!(SubtensorModule::get_rank_for_uid(netuid, uid), 0); - assert_eq!(SubtensorModule::get_trust_for_uid(netuid, uid), 0); assert_eq!(SubtensorModule::get_consensus_for_uid(netuid, uid), 0); assert_eq!( SubtensorModule::get_incentive_for_uid(netuid.into(), uid), 0 ); - assert_eq!(SubtensorModule::get_dividends_for_uid(netuid, uid), 1023); // Note D = floor(1 / 64 * 65_535) = 1023 + assert_eq!(SubtensorModule::get_dividends_for_uid(netuid, uid), 1023); // floor(1 / 64 * 65_535) assert_eq!( SubtensorModule::get_emission_for_uid(netuid, uid), 7812500.into() - ); // Note E = 0.5 / 200 * 1_000_000_000 = 7_812_500 + ); // 0.5 / 200 * 1_000_000_000 assert_eq!(bonds[uid as usize][validator], 0.0); assert_eq!(bonds[uid as usize][server], I32F32::from_num(65_535)); - // Note B_ij = floor(1 / 64 * 65_535) / 65_535 = 1023 / 65_535, then max-upscaled to 65_535 } for uid in servers { assert_eq!( SubtensorModule::get_total_stake_for_hotkey(&(U256::from(uid))), TaoCurrency::ZERO ); - assert_eq!(SubtensorModule::get_rank_for_uid(netuid, uid), 146); // Note R = floor(1 / (512 - 64) * 65_535) = 146 - assert_eq!(SubtensorModule::get_trust_for_uid(netuid, uid), 65535); - assert_eq!(SubtensorModule::get_consensus_for_uid(netuid, uid), 146); // Note C = floor(1 / (512 - 64) * 65_535) = 146 + assert_eq!(SubtensorModule::get_consensus_for_uid(netuid, uid), 146); assert_eq!( SubtensorModule::get_incentive_for_uid(netuid.into(), uid), 146 - ); // Note I = floor(1 / (512 - 64) * 65_535) = 146 + ); // floor(1 / (512 - 64) * 65_535) assert_eq!(SubtensorModule::get_dividends_for_uid(netuid, uid), 0); assert_eq!( SubtensorModule::get_emission_for_uid(netuid, uid), 1116071.into() - ); // Note E = floor(0.5 / (512 - 64) * 1_000_000_000) = 1_116_071 + ); // floor(0.5 / (512 - 64) * 1_000_000_000) assert_eq!(bonds[uid as usize][validator], 0.0); assert_eq!(bonds[uid as usize][server], 0.0); } @@ -3807,16 +3802,16 @@ fn test_epoch_does_not_mask_outside_window_but_masks_inside() { SubtensorModule::epoch(netuid, 1_000.into()); assert!( - SubtensorModule::get_rank_for_uid(netuid, 1) > 0, + SubtensorModule::get_incentive_for_uid(netuid.into(), 1) > 0, "UID-1 (old) unmasked" ); assert_eq!( - SubtensorModule::get_rank_for_uid(netuid, 2), + SubtensorModule::get_incentive_for_uid(netuid.into(), 2), 0, "UID-2 (inside window) masked" ); assert_eq!( - SubtensorModule::get_rank_for_uid(netuid, 3), + SubtensorModule::get_incentive_for_uid(netuid.into(), 3), 0, "UID-3 (inside window) masked" ); diff --git a/pallets/subtensor/src/tests/epoch_logs.rs b/pallets/subtensor/src/tests/epoch_logs.rs index 0c95857346..38e3a1b734 100644 --- a/pallets/subtensor/src/tests/epoch_logs.rs +++ b/pallets/subtensor/src/tests/epoch_logs.rs @@ -197,7 +197,6 @@ fn test_simple() { assert!(has( "Combined Emission: [AlphaCurrency(500000000), AlphaCurrency(500000000)]" )); - assert!(has("Pruning Scores: [0.5, 0.5]")); assert!(!has("math error:")); }); } @@ -460,16 +459,10 @@ fn test_validators_weight_two_distinct_servers() { assert!(has("Weights (mask+norm): [[(3, 1), (4, 0)], [(3, 0), (4, 1)], [(3, 1), (4, 0)], [], []]")); // downstream signals present - assert!(has("Ranks (before): [0, 0, 0, 0.6666666665, 0.3333333333]")); assert!(has("Consensus: [0, 0, 0, 1, 0]")); assert!(has("Validator Trust: [1, 0, 1, 0, 0]")); - assert!(has("Ranks (after): [0, 0, 0, 0.6666666665, 0]")); - assert!(has("Trust: [0, 0, 0, 1, 0]")); assert!(has("Dividends: [0.5, 0, 0.5, 0, 0]")); assert!(has("Normalized Combined Emission: [0.25, 0, 0.25, 0.5, 0]")); - assert!(has("Pruning Scores: [0.25, 0, 0.25, 0.5, 0]")); - - // math is ok assert!(!has("math error:")); }); } @@ -506,8 +499,6 @@ fn test_validator_splits_weight_across_two_servers() { assert!(has("validator_permits: [true, true, true, false, false]")); assert!(has("Weights (mask+norm): [[(3, 1), (4, 0)], [(3, 0), (4, 1)], [(3, 0.5), (4, 0.5)], [], []]")); - assert!(has("Ranks (before): [0, 0, 0, 0.4999999998, 0.4999999998]")); - assert!(has("Ranks (after): [0, 0, 0, 0.333333333, 0.333333333]")); assert!(has("ΔB (norm): [[(3, 0.5), (4, 0)], [(3, 0), (4, 0.5)], [(3, 0.5), (4, 0.5)], [], []]")); assert!(has("Dividends: [0.25, 0.25, 0.5, 0, 0]")); assert!(has("Normalized Combined Emission: [0.125, 0.125, 0.25, 0.25, 0.25]")); @@ -564,8 +555,6 @@ fn epoch_mechanism_reads_weights_per_mechanism() { assert!(logs_m1.contains("Active Stake: [0.3333333333, 0.3333333333, 0.3333333333, 0, 0]")); assert!(logs_m0.contains("Weights (mask+norm): [[(3, 1)], [(4, 1)], [(3, 1)], [], []]")); assert!(logs_m1.contains("Weights (mask+norm): [[(4, 1)], [(3, 1)], [(4, 1)], [], []]")); - assert!(logs_m0.contains("Ranks (before): [0, 0, 0, 0.6666666665, 0.3333333333]")); - assert!(logs_m1.contains("Ranks (before): [0, 0, 0, 0.3333333333, 0.6666666665]")); assert!(logs_m0.contains("ΔB (norm): [[(3, 0.5)], [], [(3, 0.5)], [], []]")); assert!(logs_m1.contains("ΔB (norm): [[(4, 0.5)], [], [(4, 0.5)], [], []]")); assert!(logs_m0.contains("Normalized Combined Emission: [0.25, 0, 0.25, 0.5, 0]")); @@ -631,7 +620,6 @@ fn epoch_mechanism_three_mechanisms_separate_state() { // Check major epoch indicators assert!(l0.contains("Weights (mask+norm): [[(2, 1)], [(2, 1)], [], []]")); - assert!(l0.contains("Ranks (before): [0, 0, 1, 0]")); assert!(l0.contains("ΔB (norm): [[(2, 0.5)], [(2, 0.5)], [], []]")); assert!(l0.contains("Normalized Combined Emission: [0.25, 0.25, 0.5, 0]")); @@ -640,12 +628,10 @@ fn epoch_mechanism_three_mechanisms_separate_state() { "Weights (mask+norm): [[(2, 0.5), (3, 0.5)], [(2, 0.5), (3, 0.5)], [], []]" ) ); - assert!(l1.contains("Ranks (before): [0, 0, 0.5, 0.5]")); assert!(l1.contains("ΔB (norm): [[(2, 0.5), (3, 0.5)], [(2, 0.5), (3, 0.5)], [], []]")); assert!(l1.contains("Normalized Combined Emission: [0.25, 0.25, 0.25, 0.25]")); assert!(l2.contains("Weights (mask+norm): [[(3, 1)], [(3, 1)], [], []]")); - assert!(l2.contains("Ranks (before): [0, 0, 0, 1]")); assert!(l2.contains("ΔB (norm): [[(3, 0.5)], [(3, 0.5)], [], []]")); assert!(l2.contains("Normalized Combined Emission: [0.25, 0.25, 0, 0.5]")); diff --git a/pallets/subtensor/src/tests/mechanism.rs b/pallets/subtensor/src/tests/mechanism.rs index 1d86e1c17d..9e6450e09c 100644 --- a/pallets/subtensor/src/tests/mechanism.rs +++ b/pallets/subtensor/src/tests/mechanism.rs @@ -742,11 +742,8 @@ fn epoch_with_mechanisms_persists_and_aggregates_all_terms() { // Fetch persisted vectors let active = Active::::get(netuid); let emission_v = Emission::::get(netuid); - let rank_v = Rank::::get(netuid); - let trust_v = Trust::::get(netuid); let cons_v = Consensus::::get(netuid); let div_v = Dividends::::get(netuid); - let prun_v = PruningScores::::get(netuid); let vtrust_v = ValidatorTrust::::get(netuid); let vperm_v = ValidatorPermit::::get(netuid); @@ -777,15 +774,8 @@ fn epoch_with_mechanisms_persists_and_aggregates_all_terms() { assert_abs_diff_eq!(u64::from(emission_v[uid]), exp_em, epsilon = 1); // u16 terms - assert_abs_diff_eq!(rank_v[uid], wu16(t0.rank, t1.rank), epsilon = 1); - assert_abs_diff_eq!(trust_v[uid], wu16(t0.trust, t1.trust), epsilon = 1); assert_abs_diff_eq!(cons_v[uid], wu16(t0.consensus, t1.consensus), epsilon = 1); assert_abs_diff_eq!(div_v[uid], wu16(t0.dividend, t1.dividend), epsilon = 1); - assert_abs_diff_eq!( - prun_v[uid], - wu16(t0.pruning_score, t1.pruning_score), - epsilon = 1 - ); assert_abs_diff_eq!( vtrust_v[uid], wu16(t0.validator_trust, t1.validator_trust), @@ -855,7 +845,6 @@ fn neuron_dereg_cleans_weights_across_subids() { AlphaCurrency::from(3u64), ], ); - Trust::::insert(netuid, vec![11u16, 99u16, 33u16]); // NOTE: trust is no longer cleared Consensus::::insert(netuid, vec![21u16, 88u16, 44u16]); Dividends::::insert(netuid, vec![7u16, 77u16, 17u16]); @@ -884,7 +873,6 @@ fn neuron_dereg_cleans_weights_across_subids() { assert_eq!(e[1], 0u64.into()); assert_eq!(e[2], 3u64.into()); - // Trust is no longer mutated by clear_neuron, so do not assert on it. let c = Consensus::::get(netuid); assert_eq!(c, vec![21, 0, 44]); @@ -919,7 +907,6 @@ fn clear_neuron_handles_absent_rows_gracefully() { // Minimal vectors with non-zero at index 0 (we will clear UID=0) Emission::::insert(netuid, vec![AlphaCurrency::from(5u64)]); - Trust::::insert(netuid, vec![5u16]); // NOTE: trust is no longer cleared by clear_neuron Consensus::::insert(netuid, vec![6u16]); Dividends::::insert(netuid, vec![7u16]); @@ -932,7 +919,7 @@ fn clear_neuron_handles_absent_rows_gracefully() { Emission::::get(netuid), vec![AlphaCurrency::from(0u64)] ); - // Do NOT assert on Trust; clear_neuron no longer mutates it. + assert_eq!(Consensus::::get(netuid), vec![0u16]); assert_eq!(Dividends::::get(netuid), vec![0u16]); }); From 23e40a78ff7735fabe98e71f8f51e555271f9b96 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 29 Oct 2025 10:55:35 -0700 Subject: [PATCH 4/6] add migration to wipe rank, trust, pruning_score --- pallets/subtensor/src/macros/hooks.rs | 4 +- .../migrate_clear_rank_trust_pruning_maps.rs | 73 +++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + pallets/subtensor/src/tests/migration.rs | 115 ++++++++++++++++++ 4 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/migrations/migrate_clear_rank_trust_pruning_maps.rs diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 037d27900e..dfed1d391f 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -157,7 +157,9 @@ mod hooks { // Migrate AutoStakeDestinationColdkeys .saturating_add(migrations::migrate_auto_stake_destination::migrate_auto_stake_destination::()) // Migrate Kappa to default (0.5) - .saturating_add(migrations::migrate_kappa_map_to_default::migrate_kappa_map_to_default::()); + .saturating_add(migrations::migrate_kappa_map_to_default::migrate_kappa_map_to_default::()) + // Remove Trust, Rank, and Pruning Score + .saturating_add(migrations::migrate_clear_rank_trust_pruning_maps::migrate_clear_rank_trust_pruning_maps::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_clear_rank_trust_pruning_maps.rs b/pallets/subtensor/src/migrations/migrate_clear_rank_trust_pruning_maps.rs new file mode 100644 index 0000000000..b661bf2e0c --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_clear_rank_trust_pruning_maps.rs @@ -0,0 +1,73 @@ +use super::*; +use frame_support::{traits::Get, weights::Weight}; +use log; +use scale_info::prelude::string::String; + +/// Remove all keys from Rank, Trust, and PruningScores. +pub fn migrate_clear_rank_trust_pruning_maps() -> Weight { + let mig_name: Vec = b"clear_rank_trust_pruning_maps".to_vec(); + let mig_name_str = String::from_utf8_lossy(&mig_name); + + // 1 read for the HasMigrationRun flag + let mut total_weight = T::DbWeight::get().reads(1); + + // Run-once guard + if HasMigrationRun::::get(&mig_name) { + log::info!("Migration '{mig_name_str}' already executed - skipping"); + return total_weight; + } + + log::info!("Running migration '{mig_name_str}'"); + + let mut total_reads: u64 = 0; + let mut total_writes: u64 = 0; + + // ------------------------------ + // 1) Rank: collect keys, then remove + // ------------------------------ + let rank_keys: Vec = Rank::::iter_keys().collect(); + let rank_removed = rank_keys.len() as u64; + total_reads = total_reads.saturating_add(rank_removed); + for k in rank_keys { + Rank::::remove(k); + total_writes = total_writes.saturating_add(1); + } + + // ------------------------------ + // 2) Trust: collect keys, then remove + // ------------------------------ + let trust_keys: Vec = Trust::::iter_keys().collect(); + let trust_removed = trust_keys.len() as u64; + total_reads = total_reads.saturating_add(trust_removed); + for k in trust_keys { + Trust::::remove(k); + total_writes = total_writes.saturating_add(1); + } + + // ------------------------------ + // 3) PruningScores: collect keys, then remove + // ------------------------------ + let ps_keys: Vec = PruningScores::::iter_keys().collect(); + let ps_removed = ps_keys.len() as u64; + total_reads = total_reads.saturating_add(ps_removed); + for k in ps_keys { + PruningScores::::remove(k); + total_writes = total_writes.saturating_add(1); + } + + // Accumulate reads/writes into the total weight + total_weight = + total_weight.saturating_add(T::DbWeight::get().reads_writes(total_reads, total_writes)); + + log::info!("Rank wipe: removed={rank_removed}"); + log::info!("Trust wipe: removed={trust_removed}"); + log::info!("PruningScores wipe: removed={ps_removed}"); + + // Mark migration as done + HasMigrationRun::::insert(&mig_name, true); + total_weight = total_weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!("Migration '{mig_name_str}' completed"); + + total_weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index b74c8e8608..5c745bb7f1 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -6,6 +6,7 @@ use sp_io::hashing::twox_128; use sp_io::storage::clear_prefix; pub mod migrate_auto_stake_destination; pub mod migrate_chain_identity; +pub mod migrate_clear_rank_trust_pruning_maps; pub mod migrate_coldkey_swap_scheduled; pub mod migrate_commit_reveal_settings; pub mod migrate_commit_reveal_v2; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 13de9501c9..d0551737dc 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -2362,3 +2362,118 @@ fn test_migrate_kappa_map_to_default() { ); }); } + +#[test] +fn test_migrate_clear_rank_trust_pruning_maps_removes_entries() { + new_test_ext(1).execute_with(|| { + // ------------------------------ + // 0) Constants + // ------------------------------ + const MIG_NAME: &[u8] = b"clear_rank_trust_pruning_maps"; + let empty: Vec = EmptyU16Vec::::get(); + + // ------------------------------ + // 1) Pre-state: seed using the correct key type (NetUid) + // ------------------------------ + let n0: NetUid = 0u16.into(); + let n1: NetUid = 1u16.into(); + let n2: NetUid = 42u16.into(); + + // Rank: n0 non-empty, n1 explicitly empty, n2 absent + Rank::::insert(n0, vec![10, 20, 30]); + Rank::::insert(n1, Vec::::new()); + + // Trust: n0 non-empty, n2 non-empty + Trust::::insert(n0, vec![7]); + Trust::::insert(n2, vec![1, 2, 3]); + + // PruningScores: n0 non-empty, n1 empty, n2 non-empty + PruningScores::::insert(n0, vec![5, 5, 5]); + PruningScores::::insert(n1, Vec::::new()); + PruningScores::::insert(n2, vec![9]); + + // Sanity: preconditions (keys should exist where inserted) + assert!(Rank::::contains_key(n0)); + assert!(Rank::::contains_key(n1)); + assert!(!Rank::::contains_key(n2)); + + assert!(Trust::::contains_key(n0)); + assert!(!Trust::::contains_key(n1)); + assert!(Trust::::contains_key(n2)); + + assert!(PruningScores::::contains_key(n0)); + assert!(PruningScores::::contains_key(n1)); + assert!(PruningScores::::contains_key(n2)); + + assert!( + !HasMigrationRun::::get(MIG_NAME.to_vec()), + "migration flag should be false before run" + ); + + // ------------------------------ + // 2) Run migration + // ------------------------------ + let w = crate::migrations::migrate_clear_rank_trust_pruning_maps::migrate_clear_rank_trust_pruning_maps::(); + assert!(!w.is_zero(), "weight must be non-zero"); + + // ------------------------------ + // 3) Verify: all entries removed (no keys present) + // ------------------------------ + assert!( + HasMigrationRun::::get(MIG_NAME.to_vec()), + "migration flag not set" + ); + + // Rank: all removed + assert!( + !Rank::::contains_key(n0), + "Rank[n0] should be removed" + ); + assert!( + !Rank::::contains_key(n1), + "Rank[n1] should be removed" + ); + assert!( + !Rank::::contains_key(n2), + "Rank[n2] should remain absent" + ); + // ValueQuery still returns empty default + assert_eq!(Rank::::get(n0), empty); + assert_eq!(Rank::::get(n1), empty); + assert_eq!(Rank::::get(n2), empty); + + // Trust: all removed + assert!( + !Trust::::contains_key(n0), + "Trust[n0] should be removed" + ); + assert!( + !Trust::::contains_key(n1), + "Trust[n1] should remain absent" + ); + assert!( + !Trust::::contains_key(n2), + "Trust[n2] should be removed" + ); + assert_eq!(Trust::::get(n0), empty); + assert_eq!(Trust::::get(n1), empty); + assert_eq!(Trust::::get(n2), empty); + + // PruningScores: all removed + assert!( + !PruningScores::::contains_key(n0), + "PruningScores[n0] should be removed" + ); + assert!( + !PruningScores::::contains_key(n1), + "PruningScores[n1] should be removed" + ); + assert!( + !PruningScores::::contains_key(n2), + "PruningScores[n2] should be removed" + ); + assert_eq!(PruningScores::::get(n0), empty); + assert_eq!(PruningScores::::get(n1), empty); + assert_eq!(PruningScores::::get(n2), empty); + }); +} From adcb2d87fa8f4463ea370168843e31c2bccc2b53 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Thu, 30 Oct 2025 13:53:56 -0700 Subject: [PATCH 5/6] refactor & add tests --- pallets/subtensor/src/subnets/registration.rs | 138 ++++++++---------- pallets/subtensor/src/tests/uids.rs | 92 ++++++++++++ runtime/src/lib.rs | 2 +- 3 files changed, 153 insertions(+), 79 deletions(-) diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index e3a086ff15..0157b844ba 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -1,5 +1,4 @@ use super::*; -use core::cmp::Ordering; use sp_core::{H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; use sp_runtime::Saturating; @@ -452,101 +451,84 @@ impl Pallet { } /// Determine which neuron to prune. - /// - /// Sort candidates by: (is_immune, emission, registration_block, uid) **ascending**, - /// which effectively means: - /// 1) prefer pruning non-immune first (is_immune = false comes first), - /// 2) among them, prune the lowest emission first, - /// 3) tie-break by the earliest registration block, - /// 4) then by the lowest uid. - /// - Never prune "immortal" owner hotkeys (immune-owner set). - /// UIDs must remain **non-immortal & non-immune** after pruning. - /// If no candidate satisfies this, return `None`. pub fn get_neuron_to_prune(netuid: NetUid) -> Option { - let neurons_n = Self::get_subnetwork_n(netuid); - if neurons_n == 0 { + let n = Self::get_subnetwork_n(netuid); + if n == 0 { return None; } - // Owner-owned "immortal" hotkeys (never prune). - let subnet_owner_coldkey = SubnetOwner::::get(netuid); - let immortal_hotkeys = Self::get_immune_owner_hotkeys(netuid, &subnet_owner_coldkey); - - // Snapshot emissions once to avoid repeated loads. + let owner_ck = SubnetOwner::::get(netuid); + let immortal_hotkeys = Self::get_immune_owner_hotkeys(netuid, &owner_ck); let emissions: Vec = Emission::::get(netuid); - // Build candidate set: skip immortal hotkeys entirely. - // Each item is (is_immune, emission, reg_block, uid). - let mut candidates: Vec<(bool, AlphaCurrency, u64, u16)> = Vec::new(); - for uid in 0..neurons_n { - if let Ok(hk) = Self::get_hotkey_for_net_and_uid(netuid, uid) { - if immortal_hotkeys.contains(&hk) { - continue; // never prune owner-immortal hotkeys + // Single pass: + // - count current non‑immortal & non‑immune UIDs, + // - track best non‑immune and best immune candidates separately. + let mut free_count: u16 = 0; + + // (emission, reg_block, uid) + let mut best_non_immune: Option<(AlphaCurrency, u64, u16)> = None; + let mut best_immune: Option<(AlphaCurrency, u64, u16)> = None; + + for uid in 0..n { + let hk = match Self::get_hotkey_for_net_and_uid(netuid, uid) { + Ok(h) => h, + Err(_) => continue, + }; + + // Skip owner‑immortal hotkeys entirely. + if immortal_hotkeys.contains(&hk) { + continue; + } + + let is_immune = Self::get_neuron_is_immune(netuid, uid); + let emission = emissions + .get(uid as usize) + .cloned() + .unwrap_or(AlphaCurrency::ZERO); + let reg_block = Self::get_neuron_block_at_registration(netuid, uid); + + // Helper to decide if (e, b, u) beats the current best. + let consider = |best: &mut Option<(AlphaCurrency, u64, u16)>| match best { + None => *best = Some((emission, reg_block, uid)), + Some((be, bb, bu)) => { + let better = if emission != *be { + emission < *be + } else if reg_block != *bb { + reg_block < *bb + } else { + uid < *bu + }; + if better { + *best = Some((emission, reg_block, uid)); + } } - let is_immune = Self::get_neuron_is_immune(netuid, uid); - let emission = emissions - .get(uid as usize) - .cloned() - .unwrap_or(AlphaCurrency::ZERO); - let reg_block = Self::get_neuron_block_at_registration(netuid, uid); - candidates.push((is_immune, emission, reg_block, uid)); + }; + + if is_immune { + consider(&mut best_immune); + } else { + free_count = free_count.saturating_add(1); + consider(&mut best_non_immune); } } - if candidates.is_empty() { + // No candidates left after filtering out owner‑immortal hotkeys. + if best_non_immune.is_none() && best_immune.is_none() { return None; } - // Safety floor for non-immortal & non-immune UIDs. + // Safety floor for non‑immortal & non‑immune UIDs. let min_free: u16 = Self::get_min_non_immune_uids(netuid); + let can_prune_non_immune = free_count > min_free; - // Count current "free" (non-immortal AND non-immune) UIDs in candidates. - // (Immortals were already filtered out above.) - let free_count: u16 = candidates - .iter() - .filter(|(is_immune, _e, _b, _u)| !*is_immune) - .count() as u16; - - // Sort by (is_immune, emission, registration_block, uid), ascending. - candidates.sort_by(|a, b| { - // a = (is_immune_a, emission_a, block_a, uid_a) - // b = (is_immune_b, emission_b, block_b, uid_b) - // tuple-lexicographic order is fine if AlphaCurrency implements Ord - let ord_immune = a.0.cmp(&b.0); // false (non-immune) first - if ord_immune != Ordering::Equal { - return ord_immune; - } - let ord_emission = a.1.cmp(&b.1); // lowest emission first - if ord_emission != Ordering::Equal { - return ord_emission; - } - let ord_block = a.2.cmp(&b.2); // earliest registration first - if ord_block != Ordering::Equal { - return ord_block; - } - a.3.cmp(&b.3) // lowest uid first - }); - - // Pick the best candidate that **does not** violate the min-free safety floor. - for (is_immune, _e, _b, uid) in candidates.iter().copied() { - if !is_immune { - // This is a non-immune candidate. We can remove it only if, - // after pruning, free_count - 1 >= min_free. - if free_count > min_free { - return Some(uid); - } else { - // Protected by the safety floor; try the next candidate. - continue; - } - } else { - // Immune candidate is allowed (owner-immortal were filtered out earlier), - // and removing it does not affect the "free" count. + // Prefer non‑immune if allowed; otherwise fall back to immune. + if can_prune_non_immune { + if let Some((_, _, uid)) = best_non_immune { return Some(uid); } } - - // No candidate can be pruned without violating the safety floor. - None + best_immune.map(|(_, _, uid)| uid) } /// Determine whether the given hash satisfies the given difficulty. diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index ecee4c7bcb..69dd003779 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -573,3 +573,95 @@ fn test_get_neuron_to_prune_owner_pruned_if_not_in_sn_owner_hotkey_map() { ); }); } + +#[test] +fn test_prune_respects_min_non_immune_floor_prefers_immune() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(2); + add_network(netuid, 10_000, 0); + ImmuneOwnerUidsLimit::::insert(netuid, 0); + + MaxRegistrationsPerBlock::::insert(netuid, 1024); + SubtensorModule::set_target_registrations_per_interval(netuid, u16::MAX); + + let immunity_period: u64 = 1000; + SubtensorModule::set_immunity_period(netuid, immunity_period as u16); + + // Register three neurons, each in its own block so the per‑block counter resets. + for i in 0..3 { + register_ok_neuron(netuid, U256::from(10_000 + i), U256::from(20_000 + i), 0); + step_block(1); + } + + // Jump block height forward past immunity for the first 3, without iterating 1000 blocks. + let target = frame_system::Pallet::::block_number() + immunity_period + 5; + frame_system::Pallet::::set_block_number(target - 1); + step_block(1); + + // Register a 4th neuron now — it will be immune. + register_ok_neuron(netuid, U256::from(99_999), U256::from(88_888), 0); + + SubtensorModule::set_min_non_immune_uids(netuid, 3); + + // With floor in place (3 non‑immune + 1 immune), we must prune the immune candidate (uid = 3). + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(3)); + }); +} + +#[test] +fn test_prune_tie_breakers_non_immune_emission_block_uid() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(3); + add_network(netuid, 1, 0); + + ImmuneOwnerUidsLimit::::insert(netuid, 0); + SubtensorModule::set_immunity_period(netuid, 0); + + // Register 3 neurons; registration blocks ascend (0,1,2). + for i in 0..3 { + register_ok_neuron(netuid, U256::from(30_000 + i), U256::from(40_000 + i), 0); + step_block(1); + } + + // Allow pruning of non-immune. + SubtensorModule::set_min_non_immune_uids(netuid, 0); + + // Equalize emissions across all 3. + Emission::::mutate(netuid, |v| { + for e in v.iter_mut() { + *e = 10u64.into(); + } + }); + + // Since emission ties, the earliest registration (uid=0) should be pruned. + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), Some(0)); + }); +} + +#[test] +fn test_prune_all_owner_immortal_returns_none() { + new_test_ext(1).execute_with(|| { + let netuid = NetUid::from(4); + + let owner_ck = U256::from(7777); + let owner_hk_0 = U256::from(9001); + let owner_hk_1 = U256::from(9002); + let owner_hk_2 = U256::from(9003); + + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, owner_ck); + + register_ok_neuron(netuid, owner_hk_0, owner_ck, 0); + register_ok_neuron(netuid, owner_hk_1, owner_ck, 0); + register_ok_neuron(netuid, owner_hk_2, owner_ck, 0); + + Owner::::insert(owner_hk_0, owner_ck); + Owner::::insert(owner_hk_1, owner_ck); + Owner::::insert(owner_hk_2, owner_ck); + OwnedHotkeys::::insert(owner_ck, vec![owner_hk_0, owner_hk_1, owner_hk_2]); + + ImmuneOwnerUidsLimit::::insert(netuid, 10); + + assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), None); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a4ee4a3b93..d8277164e2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -220,7 +220,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 333, + spec_version: 334, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From 984bc37216c0ee61969d883cddd3d8cd67ce2523 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 10 Nov 2025 18:56:11 +0000 Subject: [PATCH 6/6] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index d534dbb0c6..ae7f0fbb7f 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1016,8 +1016,8 @@ mod dispatches { /// #[pallet::call_index(6)] #[pallet::weight((Weight::from_parts(197_900_000, 0) - .saturating_add(T::DbWeight::get().reads(27_u64)) - .saturating_add(T::DbWeight::get().writes(23)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(24_u64)) + .saturating_add(T::DbWeight::get().writes(20_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register( origin: OriginFor, netuid: NetUid, @@ -1033,8 +1033,8 @@ mod dispatches { /// Register the hotkey to root network #[pallet::call_index(62)] #[pallet::weight((Weight::from_parts(135_900_000, 0) - .saturating_add(T::DbWeight::get().reads(22_u64)) - .saturating_add(T::DbWeight::get().writes(19_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(19_u64)) + .saturating_add(T::DbWeight::get().writes(16_u64)), DispatchClass::Normal, Pays::Yes))] pub fn root_register(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_root_register(origin, hotkey) } @@ -1042,8 +1042,8 @@ mod dispatches { /// User register a new subnetwork via burning token #[pallet::call_index(7)] #[pallet::weight((Weight::from_parts(354_200_000, 0) - .saturating_add(T::DbWeight::get().reads(50_u64)) - .saturating_add(T::DbWeight::get().writes(43)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(47_u64)) + .saturating_add(T::DbWeight::get().writes(40_u64)), DispatchClass::Normal, Pays::Yes))] pub fn burned_register( origin: OriginFor, netuid: NetUid, @@ -1230,8 +1230,8 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(59)] #[pallet::weight((Weight::from_parts(235_400_000, 0) - .saturating_add(T::DbWeight::get().reads(39_u64)) - .saturating_add(T::DbWeight::get().writes(56_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(36_u64)) + .saturating_add(T::DbWeight::get().writes(53_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) } @@ -1517,8 +1517,8 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(234_200_000, 0) - .saturating_add(T::DbWeight::get().reads(38_u64)) - .saturating_add(T::DbWeight::get().writes(55_u64)), DispatchClass::Normal, Pays::Yes))] + .saturating_add(T::DbWeight::get().reads(35_u64)) + .saturating_add(T::DbWeight::get().writes(52_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network_with_identity( origin: OriginFor, hotkey: T::AccountId,