From 14a8758cef72e039c8a851afe2dd1b8992724098 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 15 Feb 2025 12:32:59 -0500 Subject: [PATCH 1/6] make set diff only root --- pallets/admin-utils/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 28d1c9a7de..0d3232d894 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -653,7 +653,7 @@ pub mod pallet { netuid: u16, difficulty: u64, ) -> DispatchResult { - pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + ensure_root(origin)?; ensure!( pallet_subtensor::Pallet::::if_subnet_exist(netuid), Error::::SubnetDoesNotExist From 4039b3bb2abfafb8e7c422b8687d785458cb09df Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 15 Feb 2025 13:10:13 -0500 Subject: [PATCH 2/6] add test for set diff no owner --- pallets/admin-utils/src/tests/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 03fb1063e7..1bf2209ec1 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -643,6 +643,18 @@ fn test_sudo_set_difficulty() { to_be_set )); assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), to_be_set); + + // Test that SN owner can't set difficulty + pallet_subtensor::SubnetOwner::::insert(netuid, U256::from(1)); + assert_eq!( + AdminUtils::sudo_set_difficulty( + <::RuntimeOrigin>::signed(U256::from(1)), + netuid, + init_value + ), + Err(DispatchError::BadOrigin) + ); + assert_eq!(SubtensorModule::get_difficulty_as_u64(netuid), to_be_set); // no change }); } From 96e05ea942f0b7c31ced979b76796ffc2f7e4645 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 15 Feb 2025 13:10:22 -0500 Subject: [PATCH 3/6] add migration for min diff --- pallets/subtensor/src/macros/hooks.rs | 4 +- .../migrations/migrate_set_min_difficulty.rs | 52 +++++++++++++++++++ pallets/subtensor/src/migrations/mod.rs | 1 + 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 pallets/subtensor/src/migrations/migrate_set_min_difficulty.rs diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index 106f64456a..3a0a0ecb16 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -81,7 +81,9 @@ mod hooks { // Upgrade identities to V2 .saturating_add(migrations::migrate_identities_v2::migrate_identities_to_v2::()) // Set the min burn across all subnets to a new minimum - .saturating_add(migrations::migrate_set_min_burn::migrate_set_min_burn::()); + .saturating_add(migrations::migrate_set_min_burn::migrate_set_min_burn::()) + // Set the min difficulty across all subnets to a new minimum + .saturating_add(migrations::migrate_set_min_difficulty::migrate_set_min_difficulty::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_set_min_difficulty.rs b/pallets/subtensor/src/migrations/migrate_set_min_difficulty.rs new file mode 100644 index 0000000000..6d859925ae --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_set_min_difficulty.rs @@ -0,0 +1,52 @@ +use alloc::string::String; + +use frame_support::IterableStorageMap; +use frame_support::{traits::Get, weights::Weight}; + +use super::*; + +pub fn migrate_set_min_difficulty() -> Weight { + let migration_name = b"migrate_set_min_difficulty".to_vec(); + + // Initialize the weight with one read operation. + let mut weight = T::DbWeight::get().reads(1); + + // Check if the migration has already run + if HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{:?}' has already run. Skipping.", + migration_name + ); + return weight; + } + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + let netuids: Vec = as IterableStorageMap>::iter() + .map(|(netuid, _)| netuid) + .collect(); + weight = weight.saturating_add(T::DbWeight::get().reads(netuids.len() as u64)); + + for netuid in netuids.iter().clone() { + if *netuid == 0 { + continue; + } + // Set min difficulty to 10 million for all subnets + Pallet::::set_min_difficulty(*netuid, 10_000_000); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + } + + // Mark the migration as completed + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + "Migration '{:?}' completed.", + String::from_utf8_lossy(&migration_name) + ); + + // Return the migration weight. + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 8185226e4b..e16adf46aa 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -12,6 +12,7 @@ pub mod migrate_populate_owned_hotkeys; pub mod migrate_populate_staking_hotkeys; pub mod migrate_rao; pub mod migrate_set_min_burn; +pub mod migrate_set_min_difficulty; pub mod migrate_stake_threshold; pub mod migrate_subnet_volume; pub mod migrate_to_v1_separate_emission; From e8d211ac176a599bc30ea548032d8e672dabfc32 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sat, 15 Feb 2025 13:11:32 -0500 Subject: [PATCH 4/6] bump spec --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 28f4fabc54..1b86426bb2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -229,7 +229,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: 236, + spec_version: 237, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, From cb03dd6d7301ff39e9bf411d68e5a8a732310c3c Mon Sep 17 00:00:00 2001 From: camfairchild Date: Sat, 15 Feb 2025 17:49:17 -0500 Subject: [PATCH 5/6] only allow top-stake SN owner hk to stay immune --- pallets/subtensor/src/subnets/registration.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/subnets/registration.rs b/pallets/subtensor/src/subnets/registration.rs index 009ec6841b..f38a4e94a0 100644 --- a/pallets/subtensor/src/subnets/registration.rs +++ b/pallets/subtensor/src/subnets/registration.rs @@ -2,6 +2,7 @@ use super::*; use sp_core::{H256, U256}; use sp_io::hashing::{keccak_256, sha2_256}; use sp_runtime::Saturating; +use substrate_fixed::types::I64F64; use system::pallet_prelude::BlockNumberFor; const LOG_TARGET: &str = "runtime::subtensor::registration"; @@ -424,13 +425,32 @@ impl Pallet { return 0; // If there are no neurons in this network. } + // Get SN owner top stake hotkey + let mut top_stake_sn_owner_hotkey: Option = None; + let mut max_stake_weight: I64F64 = I64F64::from_num(-1); for neuron_uid in 0..neurons_n { - // Do not deregister the owner if let Ok(hotkey) = Self::get_hotkey_for_net_and_uid(netuid, neuron_uid) { let coldkey = Self::get_owning_coldkey_for_hotkey(&hotkey); - if Self::get_subnet_owner(netuid) == coldkey { + if Self::get_subnet_owner(netuid) != coldkey { continue; } + + let stake_weights = Self::get_stake_weights_for_hotkey_on_subnet(&hotkey, netuid); + if stake_weights.0 > max_stake_weight { + max_stake_weight = stake_weights.0; + top_stake_sn_owner_hotkey = Some(hotkey); + } + } + } + + for neuron_uid in 0..neurons_n { + // Do not deregister the owner's top-stake hotkey + if let Ok(hotkey) = Self::get_hotkey_for_net_and_uid(netuid, neuron_uid) { + if let Some(ref top_sn_owner_hotkey) = top_stake_sn_owner_hotkey { + if top_sn_owner_hotkey == &hotkey { + continue; + } + } } let pruning_score: u16 = Self::get_pruning_score_for_uid(netuid, neuron_uid); From 7ee8f7465a6e6255e1e57e95bff215fe934c8ac2 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Sat, 15 Feb 2025 18:27:22 -0500 Subject: [PATCH 6/6] add tests --- pallets/subtensor/src/tests/uids.rs | 170 ++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/pallets/subtensor/src/tests/uids.rs b/pallets/subtensor/src/tests/uids.rs index acf4057263..cc72f73a78 100644 --- a/pallets/subtensor/src/tests/uids.rs +++ b/pallets/subtensor/src/tests/uids.rs @@ -5,6 +5,7 @@ use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; +use substrate_fixed::types::I64F64; /******************************************** tests for uids.rs file @@ -335,3 +336,172 @@ fn test_get_neuron_to_prune_owner_not_pruned() { ); }); } + +#[test] +fn test_get_neuron_to_prune_owner_pruned_if_not_top_stake_owner_hotkey() { + new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(123); + let owner_coldkey = U256::from(999); + let other_owner_hotkey = U256::from(456); + + let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + + SubtensorModule::set_max_registrations_per_block(netuid, 100); + SubtensorModule::set_target_registrations_per_interval(netuid, 100); + SubnetOwner::::insert(netuid, owner_coldkey); + + 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_ok_neuron(netuid, other_owner_hotkey, owner_coldkey, 0); + let other_owner_uid = + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &other_owner_hotkey) + .expect("Should be registered"); + + let additional_hotkey_1 = U256::from(1000); + let additional_coldkey_1 = U256::from(2000); + + let additional_hotkey_2 = U256::from(1001); + let additional_coldkey_2 = U256::from(2001); + + register_ok_neuron(netuid, additional_hotkey_1, additional_coldkey_1, 1); + let uid_2 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &additional_hotkey_1) + .expect("Should be registered"); + + register_ok_neuron(netuid, additional_hotkey_2, additional_coldkey_2, 2); + 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); + + let pruned_uid = SubtensorModule::get_neuron_to_prune(netuid); + assert_eq!(pruned_uid, other_owner_uid, "Should prune the owner"); + + // Give the owner's other hotkey some stake + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &other_owner_hotkey, + &owner_coldkey, + netuid, + 1000, + ); + + // 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); + + let pruned_uid = SubtensorModule::get_neuron_to_prune(netuid); + + // - The pruned UID must be `uid_1` (score=1). + // - The owner's UID remains unpruned. + assert_eq!( + pruned_uid, owner_uid, + "Should prune the owner, not the top-stake owner hotkey and not the additional hotkeys" + ); + }); +} + +#[test] +fn test_get_neuron_to_prune_owner_pruned_if_not_top_stake_owner_hotkey_chk() { + new_test_ext(1).execute_with(|| { + let owner_hotkey = U256::from(123); + let owner_coldkey = U256::from(999); + let other_owner_hotkey = U256::from(456); + let parent_hotkey = U256::from(4567); + let parent_coldkey = U256::from(4568); + + let netuid = add_dynamic_network(&owner_hotkey, &owner_coldkey); + + SubtensorModule::set_max_registrations_per_block(netuid, 100); + SubtensorModule::set_target_registrations_per_interval(netuid, 100); + SubnetOwner::::insert(netuid, owner_coldkey); + + 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_ok_neuron(netuid, other_owner_hotkey, owner_coldkey, 0); + let other_owner_uid = + SubtensorModule::get_uid_for_net_and_hotkey(netuid, &other_owner_hotkey) + .expect("Should be registered"); + + let additional_hotkey_1 = U256::from(1000); + let additional_coldkey_1 = U256::from(2000); + + let additional_hotkey_2 = U256::from(1001); + let additional_coldkey_2 = U256::from(2001); + + register_ok_neuron(netuid, additional_hotkey_1, additional_coldkey_1, 1); + let uid_2 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &additional_hotkey_1) + .expect("Should be registered"); + + register_ok_neuron(netuid, additional_hotkey_2, additional_coldkey_2, 2); + let uid_3 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &additional_hotkey_2) + .expect("Should be registered"); + + register_ok_neuron(netuid, parent_hotkey, parent_coldkey, 3); + let uid_4: u16 = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &parent_hotkey) + .expect("Should be registered"); + + // Give parent key some stake + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &parent_hotkey, + &parent_coldkey, + netuid, + 10_000_000, + ); + + 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); + + // Ensure parent key is not pruned + SubtensorModule::set_pruning_score_for_uid(netuid, uid_4, 10_000); + + let pruned_uid = SubtensorModule::get_neuron_to_prune(netuid); + assert_eq!( + pruned_uid, other_owner_uid, + "Should prune the owner's other hotkey" + ); + + // Give the owner's other hotkey some CHK stake; Doesn't need to be much + mock_set_children_no_epochs( + netuid, + &parent_hotkey, + &[( + I64F64::saturating_from_num(0.1) + .saturating_mul(I64F64::saturating_from_num(u64::MAX)) + .saturating_to_num::(), + other_owner_hotkey, + )], + ); + // Check stake weight of other_owner_hotkey + let stake_weight = + SubtensorModule::get_stake_weights_for_hotkey_on_subnet(&other_owner_hotkey, netuid); + assert!(stake_weight.0 > 0); + + // 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); + SubtensorModule::set_pruning_score_for_uid(netuid, uid_4, 10_000); + + let pruned_uid = SubtensorModule::get_neuron_to_prune(netuid); + + // - The pruned UID must be `uid_1` (score=1). + // - The owner's UID remains unpruned. + assert_eq!( + pruned_uid, owner_uid, + "Should prune the owner, not the top-stake owner hotkey and not the additional hotkeys" + ); + }); +}