diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 4d2ac2104..3e42763f6 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -346,5 +346,16 @@ mod benchmarks { _(RawOrigin::Root, 5u16/*version*/)/*sudo_set_commit_reveal_version()*/; } + #[benchmark] + fn sudo_trim_to_max_allowed_uids() { + pallet_subtensor::Pallet::::init_new_network( + 1u16.into(), /*netuid*/ + 1u16, /*sudo_tempo*/ + ); + + #[extrinsic_call] + _(RawOrigin::Root, 1u16.into()/*netuid*/, 4097u16/*max_allowed_uids*/)/*sudo_trim_to_max_allowed_uids()*/; + } + //impl_benchmark_test_suite!(AdminUtils, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 245fc8a5a..a4d8ff906 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -668,6 +668,7 @@ pub mod pallet { min_burn: TaoCurrency, ) -> DispatchResult { pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -699,6 +700,7 @@ pub mod pallet { max_burn: TaoCurrency, ) -> DispatchResult { pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist @@ -1623,6 +1625,31 @@ pub mod pallet { log::debug!("CKBurnSet( burn: {burn:?} ) "); Ok(()) } + + /// Sets the maximum allowed UIDs for a subnet + #[pallet::call_index(74)] + #[pallet::weight(Weight::from_parts(15_000_000, 0) + .saturating_add(::DbWeight::get().reads(1_u64)) + .saturating_add(::DbWeight::get().writes(1_u64)))] + pub fn sudo_trim_to_max_allowed_uids( + origin: OriginFor, + netuid: NetUid, + max_n: u16, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin.clone(), netuid)?; + if let Ok(RawOrigin::Signed(who)) = origin.into() { + ensure!( + pallet_subtensor::Pallet::::passes_rate_limit_on_subnet( + &TransactionType::SetMaxAllowedUIDS, + &who, + netuid, + ), + pallet_subtensor::Error::::TxRateLimitExceeded + ); + } + pallet_subtensor::Pallet::::trim_to_max_allowed_uids(netuid, max_n)?; + Ok(()) + } } } diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index c8efd1066..a131c7e0f 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -5,7 +5,10 @@ use frame_support::{ traits::Hooks, }; use frame_system::Config; -use pallet_subtensor::{Error as SubtensorError, SubnetOwner, Tempo, WeightsVersionKeyRateLimit}; +use pallet_subtensor::{ + Error as SubtensorError, MaxRegistrationsPerBlock, Rank, SubnetOwner, + TargetRegistrationsPerInterval, Tempo, WeightsVersionKeyRateLimit, *, +}; // use pallet_subtensor::{migrations, Event}; use pallet_subtensor::Event; use sp_consensus_grandpa::AuthorityId as GrandpaId; @@ -2069,3 +2072,106 @@ fn test_sudo_set_max_burn() { ); }); } + +#[test] +fn test_trim_to_max_allowed_uids() { + new_test_ext().execute_with(|| { + let netuid = NetUid::from(1); + add_network(netuid, 10); + MaxRegistrationsPerBlock::::insert(netuid, 256); + TargetRegistrationsPerInterval::::insert(netuid, 256); + + // Add some neurons + let max_n = 32; + for i in 1..=max_n { + let n = i * 1000; + register_ok_neuron(netuid, U256::from(n), U256::from(n + i), 0); + } + + // Run some block to ensure stake weights are set + run_to_block(20); + + // Normal case + let new_max_n = 20; + assert_ok!(AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + new_max_n + )); + + // Ensure storage has been trimmed + assert_eq!(MaxAllowedUids::::get(netuid), new_max_n); + assert_eq!(Rank::::get(netuid).len(), new_max_n as usize); + assert_eq!(Trust::::get(netuid).len(), new_max_n as usize); + assert_eq!(Active::::get(netuid).len(), new_max_n as usize); + assert_eq!(Emission::::get(netuid).len(), new_max_n as usize); + assert_eq!(Consensus::::get(netuid).len(), new_max_n as usize); + assert_eq!(Incentive::::get(netuid).len(), new_max_n as usize); + assert_eq!(Dividends::::get(netuid).len(), new_max_n as usize); + assert_eq!(LastUpdate::::get(netuid).len(), new_max_n as usize); + assert_eq!(PruningScores::::get(netuid).len(), new_max_n as usize); + assert_eq!( + ValidatorTrust::::get(netuid).len(), + new_max_n as usize + ); + assert_eq!( + ValidatorPermit::::get(netuid).len(), + new_max_n as usize + ); + assert_eq!(StakeWeight::::get(netuid).len(), new_max_n as usize); + + for uid in max_n..new_max_n { + assert!(!Keys::::contains_key(netuid, uid)); + assert!(!BlockAtRegistration::::contains_key(netuid, uid)); + assert!(!Weights::::contains_key(netuid, uid)); + assert!(!Bonds::::contains_key(netuid, uid)); + } + + for uid in 0..max_n { + assert!( + Weights::::get(netuid, uid) + .iter() + .all(|(target_uid, _)| *target_uid < new_max_n), + "Found a weight with target_uid >= new_max_n" + ); + assert!( + Bonds::::get(netuid, uid) + .iter() + .all(|(target_uid, _)| *target_uid < new_max_n), + "Found a bond with target_uid >= new_max_n" + ); + } + + assert_eq!(SubnetworkN::::get(netuid), new_max_n); + + // Non existent subnet + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + NetUid::from(42), + new_max_n + ), + pallet_subtensor::Error::::SubNetworkDoesNotExist + ); + + // New max n less than lower bound + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + 15 + ), + pallet_subtensor::Error::::InvalidValue + ); + + // New max n greater than upper bound + assert_err!( + AdminUtils::sudo_trim_to_max_allowed_uids( + <::RuntimeOrigin>::root(), + netuid, + SubtensorModule::get_max_allowed_uids(netuid) + 1 + ), + pallet_subtensor::Error::::InvalidValue + ); + }); +} diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index ce4e41e35..c175261ba 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1498,7 +1498,7 @@ pub mod pallet { /// ==== Subnetwork Consensus Storage ==== /// ======================================= #[pallet::storage] // --- DMAP ( netuid ) --> stake_weight | weight for stake used in YC. - pub(super) type StakeWeight = + pub type StakeWeight = StorageMap<_, Identity, NetUid, Vec, ValueQuery, EmptyU16Vec>; #[pallet::storage] /// --- DMAP ( netuid, hotkey ) --> uid diff --git a/pallets/subtensor/src/subnets/uids.rs b/pallets/subtensor/src/subnets/uids.rs index d6b776252..cfa177002 100644 --- a/pallets/subtensor/src/subnets/uids.rs +++ b/pallets/subtensor/src/subnets/uids.rs @@ -16,17 +16,6 @@ impl Pallet { } } - /// Resets the trust, emission, consensus, incentive, dividends of the neuron to default - pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { - let neuron_index: usize = neuron_uid.into(); - Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); - Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); - Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. - } - /// Replace the neuron under this uid. pub fn replace_neuron( netuid: NetUid, @@ -107,6 +96,136 @@ impl Pallet { IsNetworkMember::::insert(new_hotkey.clone(), netuid, true); // Fill network is member. } + /// Clears (sets to default) the neuron map values fot a neuron when it is + /// removed from the subnet + pub fn clear_neuron(netuid: NetUid, neuron_uid: u16) { + let neuron_index: usize = neuron_uid.into(); + Emission::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0.into())); + Trust::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Consensus::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Incentive::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Dividends::::mutate(netuid, |v| Self::set_element_at(v, neuron_index, 0)); + Bonds::::remove(netuid, neuron_uid); // Remove bonds for Validator. + } + + pub fn trim_to_max_allowed_uids(netuid: NetUid, max_n: u16) -> DispatchResult { + // Reasonable limits + ensure!( + Self::if_subnet_exist(netuid), + Error::::SubNetworkDoesNotExist + ); + ensure!(max_n > 16, Error::::InvalidValue); + ensure!( + max_n <= Self::get_max_allowed_uids(netuid), + Error::::InvalidValue + ); + + // Set the value. + MaxAllowedUids::::insert(netuid, max_n); + + // Check if we need to trim. + let current_n: u16 = Self::get_subnetwork_n(netuid); + + // We need to trim, get rid of values between max_n and current_n. + if current_n > max_n { + let ranks: Vec = Rank::::get(netuid); + let trimmed_ranks: Vec = ranks.into_iter().take(max_n as usize).collect(); + Rank::::insert(netuid, trimmed_ranks); + + let trust: Vec = Trust::::get(netuid); + let trimmed_trust: Vec = trust.into_iter().take(max_n as usize).collect(); + Trust::::insert(netuid, trimmed_trust); + + let active: Vec = Active::::get(netuid); + let trimmed_active: Vec = active.into_iter().take(max_n as usize).collect(); + Active::::insert(netuid, trimmed_active); + + let emission: Vec = Emission::::get(netuid); + let trimmed_emission: Vec = + emission.into_iter().take(max_n as usize).collect(); + Emission::::insert(netuid, trimmed_emission); + + let consensus: Vec = Consensus::::get(netuid); + let trimmed_consensus: Vec = consensus.into_iter().take(max_n as usize).collect(); + Consensus::::insert(netuid, trimmed_consensus); + + let incentive: Vec = Incentive::::get(netuid); + let trimmed_incentive: Vec = incentive.into_iter().take(max_n as usize).collect(); + Incentive::::insert(netuid, trimmed_incentive); + + let dividends: Vec = Dividends::::get(netuid); + let trimmed_dividends: Vec = dividends.into_iter().take(max_n as usize).collect(); + Dividends::::insert(netuid, trimmed_dividends); + + let lastupdate: Vec = LastUpdate::::get(netuid); + let trimmed_lastupdate: Vec = + lastupdate.into_iter().take(max_n as usize).collect(); + LastUpdate::::insert(netuid, trimmed_lastupdate); + + let pruning_scores: Vec = PruningScores::::get(netuid); + let trimmed_pruning_scores: Vec = + pruning_scores.into_iter().take(max_n as usize).collect(); + PruningScores::::insert(netuid, trimmed_pruning_scores); + + let vtrust: Vec = ValidatorTrust::::get(netuid); + let trimmed_vtrust: Vec = vtrust.into_iter().take(max_n as usize).collect(); + ValidatorTrust::::insert(netuid, trimmed_vtrust); + + let vpermit: Vec = ValidatorPermit::::get(netuid); + let trimmed_vpermit: Vec = vpermit.into_iter().take(max_n as usize).collect(); + ValidatorPermit::::insert(netuid, trimmed_vpermit); + + let stake_weight: Vec = StakeWeight::::get(netuid); + let trimmed_stake_weight: Vec = + stake_weight.into_iter().take(max_n as usize).collect(); + StakeWeight::::insert(netuid, trimmed_stake_weight); + + for uid in max_n..current_n { + // Trim UIDs and Keys by removing entries with UID >= max_n (since UIDs are 0-indexed) + // UIDs range from 0 to current_n-1, so we remove UIDs from max_n to current_n-1 + if let Ok(hotkey) = Keys::::try_get(netuid, uid) { + Uids::::remove(netuid, &hotkey); + // Remove IsNetworkMember association for the hotkey + IsNetworkMember::::remove(&hotkey, netuid); + // Remove last hotkey emission for the hotkey + LastHotkeyEmissionOnNetuid::::remove(&hotkey, netuid); + // Remove alpha dividends for the hotkey + AlphaDividendsPerSubnet::::remove(netuid, &hotkey); + // Remove tao dividends for the hotkey + TaoDividendsPerSubnet::::remove(netuid, &hotkey); + // Trim axons, certificates, and prometheus info for removed hotkeys + Axons::::remove(netuid, &hotkey); + NeuronCertificates::::remove(netuid, &hotkey); + Prometheus::::remove(netuid, &hotkey); + } + #[allow(unknown_lints)] + Keys::::remove(netuid, uid); + // Remove block at registration for the uid + BlockAtRegistration::::remove(netuid, uid); + // Remove entire weights and bonds entries for removed UIDs + Weights::::remove(netuid, uid); + Bonds::::remove(netuid, uid); + } + + // Trim weight and bond connections to removed UIDs for remaining neurons + // UIDs 0 to max_n-1 are kept, so we iterate through these valid UIDs + for uid in 0..max_n { + Weights::::mutate(netuid, uid, |weights| { + weights.retain(|(target_uid, _)| *target_uid < max_n); + }); + Bonds::::mutate(netuid, uid, |bonds| { + bonds.retain(|(target_uid, _)| *target_uid < max_n); + }); + } + + // Update the subnetwork size + SubnetworkN::::insert(netuid, max_n); + } + + // --- Ok and done. + Ok(()) + } + /// Returns true if the uid is set on the network. /// pub fn is_uid_exist_on_network(netuid: NetUid, uid: u16) -> bool { diff --git a/pallets/subtensor/src/utils/rate_limiting.rs b/pallets/subtensor/src/utils/rate_limiting.rs index eeb5b96dd..9cb2d4ffb 100644 --- a/pallets/subtensor/src/utils/rate_limiting.rs +++ b/pallets/subtensor/src/utils/rate_limiting.rs @@ -11,6 +11,7 @@ pub enum TransactionType { RegisterNetwork, SetWeightsVersionKey, SetSNOwnerHotkey, + SetMaxAllowedUIDS, } /// Implement conversion from TransactionType to u16 @@ -23,6 +24,7 @@ impl From for u16 { TransactionType::RegisterNetwork => 3, TransactionType::SetWeightsVersionKey => 4, TransactionType::SetSNOwnerHotkey => 5, + TransactionType::SetMaxAllowedUIDS => 6, } } } @@ -36,6 +38,7 @@ impl From for TransactionType { 3 => TransactionType::RegisterNetwork, 4 => TransactionType::SetWeightsVersionKey, 5 => TransactionType::SetSNOwnerHotkey, + 6 => TransactionType::SetMaxAllowedUIDS, _ => TransactionType::Unknown, } } @@ -50,7 +53,7 @@ impl Pallet { TransactionType::SetChildren => 150, // 30 minutes TransactionType::SetChildkeyTake => TxChildkeyTakeRateLimit::::get(), TransactionType::RegisterNetwork => NetworkRateLimit::::get(), - + TransactionType::SetMaxAllowedUIDS => 7200 * 30, TransactionType::Unknown => 0, // Default to no limit for unknown types (no limit) _ => 0, } @@ -62,7 +65,6 @@ impl Pallet { TransactionType::SetWeightsVersionKey => (Tempo::::get(netuid) as u64) .saturating_mul(WeightsVersionKeyRateLimit::::get()), TransactionType::SetSNOwnerHotkey => DefaultSetSNOwnerHotkeyRateLimit::::get(), - _ => Self::get_rate_limit(tx_type), } } @@ -89,7 +91,6 @@ impl Pallet { let block: u64 = Self::get_current_block_as_u64(); let limit: u64 = Self::get_rate_limit_on_subnet(tx_type, netuid); let last_block: u64 = Self::get_last_transaction_block_on_subnet(hotkey, netuid, tx_type); - Self::check_passes_rate_limit(limit, block, last_block) }