use super::*; use frame_support::storage::IterableStorageDoubleMap; use sp_runtime::{PerU16, Percent}; use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; use sp_std::{cmp, vec}; use subtensor_runtime_common::NetUid; impl Pallet { /// Returns the number of filled slots on a network. pub fn get_subnetwork_n(netuid: NetUid) -> u16 { SubnetworkN::::get(netuid) } /// Sets value for the element at the given position if it exists. pub fn set_element_at(vec: &mut [N], position: usize, value: N) { if let Some(element) = vec.get_mut(position) { *element = value; } } /// 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())); Consensus::::mutate(netuid, |v| { Self::set_element_at(v, neuron_index, PerU16::zero()) }); for mecid in 0..MechanismCountCurrent::::get(netuid).into() { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.into()); Incentive::::mutate(netuid_index, |v| { Self::set_element_at(v, neuron_index, PerU16::zero()) }); Bonds::::remove(netuid_index, neuron_uid); // Remove bonds for Validator. // Clear weights set BY the neuron_uid Weights::::remove(netuid_index, neuron_uid); // Set weights FOR the neuron_uid to 0 let all_uids: Vec = Weights::::iter_key_prefix(netuid_index).collect(); for uid in all_uids { Weights::::mutate(netuid_index, uid, |weight_vec: &mut Vec<(u16, u16)>| { for (weight_uid, w) in weight_vec.iter_mut() { if *weight_uid == neuron_uid { *w = 0; } } }); } } Dividends::::mutate(netuid, |v| { Self::set_element_at(v, neuron_index, PerU16::zero()) }); StakeWeight::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); ValidatorTrust::::mutate(netuid, |v| { Self::set_element_at(v, neuron_index, PerU16::zero()) }); ValidatorPermit::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, false)); } /// Replace the neuron under this uid. pub fn replace_neuron( netuid: NetUid, uid_to_replace: u16, new_hotkey: &T::AccountId, block_number: u64, ) { log::debug!( "replace_neuron( netuid: {netuid:?} | uid_to_replace: {uid_to_replace:?} | new_hotkey: {new_hotkey:?} ) " ); // 1. Get the old hotkey under this position. let old_hotkey: T::AccountId = Keys::::get(netuid, uid_to_replace); // Do not replace owner hotkey from `SubnetOwnerHotkey` if let Ok(sn_owner_hotkey) = SubnetOwnerHotkey::::try_get(netuid) && sn_owner_hotkey == old_hotkey.clone() { log::warn!( "replace_neuron: Skipped replacement because neuron is the subnet owner hotkey. \ netuid: {netuid:?}, uid_to_replace: {uid_to_replace:?}, new_hotkey: {new_hotkey:?}, owner_hotkey: {sn_owner_hotkey:?}" ); return; } // 2. Remove previous set memberships. Uids::::remove(netuid, old_hotkey.clone()); Self::remove_associated_evm_address(netuid, uid_to_replace); IsNetworkMember::::remove(old_hotkey.clone(), netuid); #[allow(unknown_lints)] Keys::::remove(netuid, uid_to_replace); // 3. Create new set memberships. Self::set_active_for_uid(netuid, uid_to_replace, true); // Set to active by default. Keys::::insert(netuid, uid_to_replace, new_hotkey.clone()); // Make hotkey - uid association. Uids::::insert(netuid, new_hotkey.clone(), uid_to_replace); // Make uid - hotkey association. BlockAtRegistration::::insert(netuid, uid_to_replace, block_number); // Fill block at registration. IsNetworkMember::::insert(new_hotkey.clone(), netuid, true); // Fill network is member. // 4. Clear neuron axons, certificates and prometheus info Axons::::remove(netuid, &old_hotkey); NeuronCertificates::::remove(netuid, &old_hotkey); Prometheus::::remove(netuid, &old_hotkey); // 5. Reset new neuron's values. Self::clear_neuron(netuid, uid_to_replace); // 6. Replacement creates a new logical neuron at the reused UID, so the weights timing // state should start from this registration block. for mecid in 0..MechanismCountCurrent::::get(netuid).into() { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.into()); Self::set_last_update_for_uid(netuid_index, uid_to_replace, block_number); } } /// 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. let next_uid: u16 = Self::get_subnetwork_n(netuid); log::debug!( "append_neuron( netuid: {netuid:?} | next_uid: {next_uid:?} | new_hotkey: {new_hotkey:?} ) " ); // 2. Get and increase the uid count. SubnetworkN::::insert(netuid, next_uid.saturating_add(1)); // 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(PerU16::zero())); for mecid in 0..MechanismCountCurrent::::get(netuid).into() { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.into()); Incentive::::mutate(netuid_index, |v| v.push(PerU16::zero())); Self::set_last_update_for_uid(netuid_index, next_uid, block_number); } Dividends::::mutate(netuid, |v| v.push(PerU16::zero())); ValidatorTrust::::mutate(netuid, |v| v.push(PerU16::zero())); ValidatorPermit::::mutate(netuid, |v| v.push(false)); // 4. Insert new account information. Keys::::insert(netuid, next_uid, new_hotkey.clone()); // Make hotkey - uid association. Uids::::insert(netuid, new_hotkey.clone(), next_uid); // Make uid - hotkey association. BlockAtRegistration::::insert(netuid, next_uid, block_number); // Fill block at registration. IsNetworkMember::::insert(new_hotkey.clone(), netuid, true); // Fill network is member. } pub fn trim_to_max_allowed_uids(netuid: NetUid, max_n: u16) -> DispatchResult { // Reasonable limits ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); ensure!( max_n >= MinAllowedUids::::get(netuid), Error::::InvalidValue ); ensure!( max_n <= MaxAllowedUids::::get(netuid), Error::::InvalidValue ); MaxAllowedUids::::insert(netuid, max_n); 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 { 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::::TrimmingWouldExceedMaxImmunePercentage ); // 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)| cmp::Reverse(*emission)); let mut removed_uids = BTreeSet::new(); let mut uids_left_to_process = current_n; let mechanisms_count = MechanismCountCurrent::::get(netuid).into(); // 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 } 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(&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, neuron_uid) { Uids::::remove(netuid, &hotkey); IsNetworkMember::::remove(&hotkey, netuid); LastHotkeyEmissionOnNetuid::::remove(&hotkey, netuid); AlphaDividendsPerSubnet::::remove(netuid, &hotkey); Axons::::remove(netuid, &hotkey); NeuronCertificates::::remove(netuid, &hotkey); Prometheus::::remove(netuid, &hotkey); } // Remove all storage items associated with this uid #[allow(unknown_lints)] Keys::::remove(netuid, neuron_uid); BlockAtRegistration::::remove(netuid, neuron_uid); Self::remove_associated_evm_address(netuid, neuron_uid); for mecid in 0..mechanisms_count { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.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()); removed_uids.insert(uid); uids_left_to_process = uids_left_to_process.saturating_sub(1); } } // 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 active = Active::::get(netuid); let consensus = Consensus::::get(netuid); let dividends = Dividends::::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 len = trimmed_uids.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_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_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_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 Emission::::insert(netuid, trimmed_emissions); Active::::insert(netuid, trimmed_active); Consensus::::insert(netuid, trimmed_consensus); Dividends::::insert(netuid, trimmed_dividends); ValidatorTrust::::insert(netuid, trimmed_vtrust); ValidatorPermit::::insert(netuid, trimmed_vpermit); StakeWeight::::insert(netuid, trimmed_stake_weight); // Update incentives/lastupdates for mechanisms for mecid in 0..mechanisms_count { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.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 .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 { let old_neuron_uid = *old_uid as u16; let new_neuron_uid = *new_uid as u16; // Swap uid specific storage items to new compressed positions Keys::::swap(netuid, old_neuron_uid, netuid, new_neuron_uid); AssociatedEvmAddress::::swap(netuid, old_neuron_uid, netuid, new_neuron_uid); BlockAtRegistration::::swap(netuid, old_neuron_uid, netuid, new_neuron_uid); for mecid in 0..mechanisms_count { let netuid_index = Self::get_mechanism_storage_index(netuid, mecid.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 } }) }); } } Self::remap_associated_evm_address_index(netuid, &old_to_new_uid); // Clear the UID map for the subnet let clear_result = Uids::::clear_prefix(netuid, u32::MAX, None); // Shouldn't happen, but possible. ensure!( clear_result.maybe_cursor.is_none(), Error::::UidMapCouldNotBeCleared ); // Insert the new UIDs for new_uid in old_to_new_uid.values() { // Get the hotkey using Keys map and new UID. let hotkey = Keys::::get(netuid, *new_uid as u16); Uids::::insert(netuid, hotkey, *new_uid as u16); } // Update the subnet's uid count to reflect the new maximum SubnetworkN::::insert(netuid, max_n); } Ok(()) } /// Returns true if the uid is set on the network. /// pub fn is_uid_exist_on_network(netuid: NetUid, uid: u16) -> bool { Keys::::contains_key(netuid, uid) } /// Returns true if the hotkey holds a slot on the network. /// pub fn is_hotkey_registered_on_network(netuid: NetUid, hotkey: &T::AccountId) -> bool { Uids::::contains_key(netuid, hotkey) } /// Returs the hotkey under the network uid as a Result. Ok if the uid is taken. /// pub fn get_hotkey_for_net_and_uid( netuid: NetUid, neuron_uid: u16, ) -> Result { Keys::::try_get(netuid, neuron_uid) .map_err(|_err| Error::::HotKeyNotRegisteredInSubNet.into()) } /// Returns the uid of the hotkey in the network as a Result. Ok if the hotkey has a slot. /// pub fn get_uid_for_net_and_hotkey( netuid: NetUid, hotkey: &T::AccountId, ) -> Result { Uids::::try_get(netuid, hotkey) .map_err(|_err| Error::::HotKeyNotRegisteredInSubNet.into()) } /// Returns the stake of the uid on network or 0 if it doesnt exist. /// pub fn get_stake_for_uid_and_subnetwork(netuid: NetUid, neuron_uid: u16) -> AlphaBalance { if let Ok(hotkey) = Self::get_hotkey_for_net_and_uid(netuid, neuron_uid) { Self::get_stake_for_hotkey_on_subnet(&hotkey, netuid) } else { AlphaBalance::ZERO } } /// Return a list of all networks a hotkey is registered on. /// pub fn get_registered_networks_for_hotkey(hotkey: &T::AccountId) -> Vec { let mut all_networks: Vec = vec![]; for (network, is_registered) in as IterableStorageDoubleMap< T::AccountId, NetUid, bool, >>::iter_prefix(hotkey) { if is_registered { all_networks.push(network) } } all_networks } /// Return true if a hotkey is registered on any network. /// pub fn is_hotkey_registered_on_any_network(hotkey: &T::AccountId) -> bool { for (_, is_registered) in as IterableStorageDoubleMap< T::AccountId, NetUid, bool, >>::iter_prefix(hotkey) { if is_registered { return true; } } false } /// Return true if a hotkey is registered on specific network. /// pub fn is_hotkey_registered_on_specific_network(hotkey: &T::AccountId, netuid: NetUid) -> bool { IsNetworkMember::::contains_key(hotkey, netuid) } }