From 664e08b3bd08cf7433b6a97cca5f8fbea6823c73 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 16 Mar 2026 13:59:16 +0300 Subject: [PATCH 1/4] Add root validators as parent to subnet owner hotkey. --- pallets/subtensor/src/staking/set_children.rs | 64 +++++++++ pallets/subtensor/src/tests/children.rs | 136 ++++++++++++++++++ 2 files changed, 200 insertions(+) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index c7ebd62c04..f41a453c20 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -769,6 +769,70 @@ impl Pallet { // State cleaners (for use in migration) // TODO: Deprecate when the state is clean for a while + /// Establishes parent-child relationships between all root validators and + /// a subnet owner's hotkey on the specified subnet. + /// + /// For each validator on the root network (netuid 0), this function calls + /// `do_schedule_children` to schedule the subnet owner hotkey as a child + /// of that root validator on the given subnet, with full proportion (u64::MAX). + /// + /// # Arguments + /// * `netuid` - The subnet on which to establish relationships. + /// + /// # Returns + /// * `DispatchResult` - Ok if at least the setup completes; individual + /// scheduling failures per validator are logged but do not abort the loop. + pub fn do_set_root_validators_for_subnet(netuid: NetUid) -> DispatchResult { + // Cannot set children on root network itself. + ensure!( + !netuid.is_root(), + Error::::RegistrationNotPermittedOnRootSubnet + ); + + // Subnet must exist. + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + + // Get the subnet owner hotkey. + let subnet_owner_hotkey = SubnetOwnerHotkey::::try_get(netuid) + .map_err(|_| Error::::SubnetNotExists)?; + + // Iterate over all root validators and schedule each one as a parent + // of the subnet owner hotkey. + for (_uid, root_validator_hotkey) in Keys::::iter_prefix(NetUid::ROOT) { + // Skip if the root validator is the subnet owner hotkey itself + // (cannot be both parent and child). + if root_validator_hotkey == subnet_owner_hotkey { + continue; + } + + // Look up the coldkey that owns this root validator hotkey. + let coldkey = Self::get_owning_coldkey_for_hotkey(&root_validator_hotkey); + + // Build a signed origin from the coldkey. + let origin: T::RuntimeOrigin = + frame_system::RawOrigin::Signed(coldkey).into(); + + // Schedule the subnet owner hotkey as a child with full proportion. + let children = vec![(u64::MAX, subnet_owner_hotkey.clone())]; + + if let Err(e) = Self::do_schedule_children( + origin, + root_validator_hotkey.clone(), + netuid, + children, + ) { + log::warn!( + "Failed to schedule children for root validator {:?} on netuid {:?}: {:?}", + root_validator_hotkey, + netuid, + e + ); + } + } + + Ok(()) + } + pub fn clean_zero_childkey_vectors(weight: &mut Weight) { // Collect keys to delete first to avoid mutating while iterating. let mut to_remove: Vec<(T::AccountId, NetUid)> = Vec::new(); diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 1558c4cb6c..6029aa254c 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -4220,3 +4220,139 @@ fn test_set_child_keys_empty_vector_clears_storage() { assert!(ParentKeys::::get(child, netuid).is_empty()); }); } + +// Test that do_set_root_children_for_subnet enables a subnet owner to set weights +// by inheriting stake from root validators. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_root_children_enable_subnet_owner_set_weights --exact --show-output --nocapture +#[test] +fn test_root_children_enable_subnet_owner_set_weights() { + new_test_ext(1).execute_with(|| { + // --- Setup accounts --- + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + + let root_val_coldkey_1 = U256::from(100); + let root_val_hotkey_1 = U256::from(101); + let root_val_coldkey_2 = U256::from(200); + let root_val_hotkey_2 = U256::from(201); + + // --- Create root network and subnet --- + add_network(NetUid::ROOT, 1, 0); + let netuid = + add_dynamic_network_disable_commit_reveal(&subnet_owner_hotkey, &subnet_owner_coldkey); + + // --- Register root validators on a subnet first (required before root_register) --- + register_ok_neuron(netuid, root_val_hotkey_1, root_val_coldkey_1, 0); + register_ok_neuron(netuid, root_val_hotkey_2, root_val_coldkey_2, 0); + + // --- Register root validators on root network --- + assert_ok!(SubtensorModule::root_register( + RuntimeOrigin::signed(root_val_coldkey_1), + root_val_hotkey_1, + )); + assert_ok!(SubtensorModule::root_register( + RuntimeOrigin::signed(root_val_coldkey_2), + root_val_hotkey_2, + )); + + // Subnet owner hotkey is auto-registered on the subnet via add_dynamic_network. + + // --- Add significant stake for root validators on root and the subnet --- + let root_stake = AlphaBalance::from(1_000_000_000); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_1, + &root_val_coldkey_1, + NetUid::ROOT, + root_stake, + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_1, + &root_val_coldkey_1, + netuid, + root_stake, + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_2, + &root_val_coldkey_2, + NetUid::ROOT, + root_stake, + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_2, + &root_val_coldkey_2, + netuid, + root_stake, + ); + + // --- Set a high stake threshold so the subnet owner alone cannot set weights --- + let high_threshold = 500_000_000u64; + SubtensorModule::set_stake_threshold(high_threshold); + + // Disable rate limits for clean testing + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + + let version_key = SubtensorModule::get_weights_version_key(netuid); + let uids: Vec = vec![0]; + let values: Vec = vec![u16::MAX]; + + // Show that subnet owner CANNOT set weights (insufficient stake) --- + assert!( + !SubtensorModule::check_weights_min_stake(&subnet_owner_hotkey, netuid), + "Subnet owner should NOT have enough stake to set weights initially" + ); + assert_noop!( + SubtensorModule::set_weights( + RuntimeOrigin::signed(subnet_owner_hotkey), + netuid, + uids.clone(), + values.clone(), + version_key + ), + Error::::NotEnoughStakeToSetWeights + ); + + // Minimize the pending children cooldown via root extrinsic --- + assert_ok!(SubtensorModule::set_pending_childkey_cooldown( + RuntimeOrigin::root(), + 0, // zero block cooldown + )); + + assert_ok!(SubtensorModule::do_set_root_validators_for_subnet(netuid)); + + // Activate pending children (cooldown is 0, advance 1 block) --- + step_block(1); + SubtensorModule::do_set_pending_children(netuid); + + // Verify that child-parent relationships were created: + // Each root validator should have the subnet owner hotkey as a child on netuid + let children_1 = SubtensorModule::get_children(&root_val_hotkey_1, netuid); + assert_eq!( + children_1, + vec![(u64::MAX, subnet_owner_hotkey)], + "Root validator 1 should have subnet owner as child" + ); + let children_2 = SubtensorModule::get_children(&root_val_hotkey_2, netuid); + assert_eq!( + children_2, + vec![(u64::MAX, subnet_owner_hotkey)], + "Root validator 2 should have subnet owner as child" + ); + + // Verify that the subnet owner has both root validators as parents + let parents = SubtensorModule::get_parents(&subnet_owner_hotkey, netuid); + assert_eq!(parents.len(), 2, "Subnet owner should have 2 parents"); + + // Show that subnet owner CAN now set weights --- + assert!( + SubtensorModule::check_weights_min_stake(&subnet_owner_hotkey, netuid), + "Subnet owner should now have enough inherited stake to set weights" + ); + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(subnet_owner_hotkey), + netuid, + uids, + values, + version_key + )); + }); +} From 8b25e71cf81315be815119c60208a315362bdd60 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Mon, 16 Mar 2026 17:43:08 +0300 Subject: [PATCH 2/4] Modify subnet registration. --- pallets/subtensor/src/staking/set_children.rs | 16 +- pallets/subtensor/src/subnets/subnet.rs | 11 +- pallets/subtensor/src/tests/children.rs | 137 ++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index f41a453c20..51e91dcb0f 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -793,8 +793,8 @@ impl Pallet { ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); // Get the subnet owner hotkey. - let subnet_owner_hotkey = SubnetOwnerHotkey::::try_get(netuid) - .map_err(|_| Error::::SubnetNotExists)?; + let subnet_owner_hotkey = + SubnetOwnerHotkey::::try_get(netuid).map_err(|_| Error::::SubnetNotExists)?; // Iterate over all root validators and schedule each one as a parent // of the subnet owner hotkey. @@ -809,18 +809,14 @@ impl Pallet { let coldkey = Self::get_owning_coldkey_for_hotkey(&root_validator_hotkey); // Build a signed origin from the coldkey. - let origin: T::RuntimeOrigin = - frame_system::RawOrigin::Signed(coldkey).into(); + let origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(coldkey).into(); // Schedule the subnet owner hotkey as a child with full proportion. let children = vec![(u64::MAX, subnet_owner_hotkey.clone())]; - if let Err(e) = Self::do_schedule_children( - origin, - root_validator_hotkey.clone(), - netuid, - children, - ) { + if let Err(e) = + Self::do_schedule_children(origin, root_validator_hotkey.clone(), netuid, children) + { log::warn!( "Failed to schedule children for root validator {:?} on netuid {:?}: {:?}", root_validator_hotkey, diff --git a/pallets/subtensor/src/subnets/subnet.rs b/pallets/subtensor/src/subnets/subnet.rs index 769db17ebe..629c30384d 100644 --- a/pallets/subtensor/src/subnets/subnet.rs +++ b/pallets/subtensor/src/subnets/subnet.rs @@ -246,7 +246,16 @@ impl Pallet { Self::deposit_event(Event::SubnetIdentitySet(netuid_to_register)); } - // --- 18. Emit the NetworkAdded event. + // --- 18. Schedule root validators as parents of the subnet owner hotkey. + if let Err(e) = Self::do_set_root_validators_for_subnet(netuid_to_register) { + log::warn!( + "Failed to set root validators for netuid {:?}: {:?}", + netuid_to_register, + e + ); + } + + // --- 19. Emit the NetworkAdded event. log::info!("NetworkAdded( netuid:{netuid_to_register:?}, mechanism:{mechid:?} )"); Self::deposit_event(Event::NetworkAdded(netuid_to_register, mechid)); diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 6029aa254c..3e4d4768a0 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -4356,3 +4356,140 @@ fn test_root_children_enable_subnet_owner_set_weights() { )); }); } + +// Test that register_network automatically schedules root validators as parents of the +// subnet owner, enabling the owner to set weights after cooldown. +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_register_network_schedules_root_validators --exact --show-output --nocapture +#[test] +fn test_register_network_schedules_root_validators() { + new_test_ext(1).execute_with(|| { + // --- Setup root network and root validators --- + let root_val_coldkey_1 = U256::from(100); + let root_val_hotkey_1 = U256::from(101); + let root_val_coldkey_2 = U256::from(200); + let root_val_hotkey_2 = U256::from(201); + + add_network(NetUid::ROOT, 1, 0); + + // Root validators need to be registered on some subnet before root_register. + // Create a bootstrap subnet for that purpose. + let bootstrap_netuid = NetUid::from(1); + add_network(bootstrap_netuid, 1, 0); + register_ok_neuron(bootstrap_netuid, root_val_hotkey_1, root_val_coldkey_1, 0); + register_ok_neuron(bootstrap_netuid, root_val_hotkey_2, root_val_coldkey_2, 0); + + assert_ok!(SubtensorModule::root_register( + RuntimeOrigin::signed(root_val_coldkey_1), + root_val_hotkey_1, + )); + assert_ok!(SubtensorModule::root_register( + RuntimeOrigin::signed(root_val_coldkey_2), + root_val_hotkey_2, + )); + + // Give root validators significant stake on root and bootstrap subnet + let root_stake = AlphaBalance::from(1_000_000_000); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_1, + &root_val_coldkey_1, + NetUid::ROOT, + root_stake, + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_2, + &root_val_coldkey_2, + NetUid::ROOT, + root_stake, + ); + + // --- Minimize cooldown so pending children activate quickly --- + assert_ok!(SubtensorModule::set_pending_childkey_cooldown( + RuntimeOrigin::root(), + 0, + )); + + // --- Set a high stake threshold --- + let high_threshold = 500_000_000u64; + SubtensorModule::set_stake_threshold(high_threshold); + + // --- Register a new subnet (this should automatically call do_set_root_validators_for_subnet) --- + let subnet_owner_coldkey = U256::from(1001); + let subnet_owner_hotkey = U256::from(1002); + let lock_cost = SubtensorModule::get_network_lock_cost(); + SubtensorModule::add_balance_to_coldkey_account(&subnet_owner_coldkey, lock_cost.into()); + TotalIssuance::::mutate(|total| { + *total = total.saturating_add(lock_cost); + }); + assert_ok!(SubtensorModule::register_network( + RuntimeOrigin::signed(subnet_owner_coldkey), + subnet_owner_hotkey, + )); + + // Determine the netuid that was just created + let netuid: NetUid = (TotalNetworks::::get().saturating_sub(1)).into(); + assert_eq!( + SubnetOwnerHotkey::::get(netuid), + subnet_owner_hotkey, + "Subnet owner hotkey should be set" + ); + + // Root validators need stake on the new subnet for child stake inheritance to work + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_1, + &root_val_coldkey_1, + netuid, + root_stake, + ); + SubtensorModule::increase_stake_for_hotkey_and_coldkey_on_subnet( + &root_val_hotkey_2, + &root_val_coldkey_2, + netuid, + root_stake, + ); + + // --- Verify pending children were scheduled during registration --- + assert!( + PendingChildKeys::::contains_key(netuid, root_val_hotkey_1), + "Root validator 1 should have pending children on the new subnet" + ); + assert!( + PendingChildKeys::::contains_key(netuid, root_val_hotkey_2), + "Root validator 2 should have pending children on the new subnet" + ); + + // --- Activate pending children --- + step_block(1); + SubtensorModule::do_set_pending_children(netuid); + + // --- Verify child-parent relationships --- + let children_1 = SubtensorModule::get_children(&root_val_hotkey_1, netuid); + assert_eq!( + children_1, + vec![(u64::MAX, subnet_owner_hotkey)], + "Root validator 1 should have subnet owner as child" + ); + let children_2 = SubtensorModule::get_children(&root_val_hotkey_2, netuid); + assert_eq!( + children_2, + vec![(u64::MAX, subnet_owner_hotkey)], + "Root validator 2 should have subnet owner as child" + ); + + // --- Verify subnet owner can now set weights --- + SubtensorModule::set_weights_set_rate_limit(netuid, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, false); + let version_key = SubtensorModule::get_weights_version_key(netuid); + + assert!( + SubtensorModule::check_weights_min_stake(&subnet_owner_hotkey, netuid), + "Subnet owner should have enough inherited stake to set weights" + ); + assert_ok!(SubtensorModule::set_weights( + RuntimeOrigin::signed(subnet_owner_hotkey), + netuid, + vec![0], + vec![u16::MAX], + version_key + )); + }); +} From 4cf5f6489e2c39c41c4936d210ab3618c1c4d364 Mon Sep 17 00:00:00 2001 From: Shamil Gadelshin Date: Fri, 20 Mar 2026 13:32:52 +0300 Subject: [PATCH 3/4] Fix origin after merge --- pallets/subtensor/src/staking/set_children.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 6b110a406d..1cf6e950c7 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -809,7 +809,8 @@ impl Pallet { let coldkey = Self::get_owning_coldkey_for_hotkey(&root_validator_hotkey); // Build a signed origin from the coldkey. - let origin: T::RuntimeOrigin = frame_system::RawOrigin::Signed(coldkey).into(); + let origin: ::RuntimeOrigin = + frame_system::RawOrigin::Signed(coldkey).into(); // Schedule the subnet owner hotkey as a child with full proportion. let children = vec![(u64::MAX, subnet_owner_hotkey.clone())]; From 02b447e6a582def5d8c6e508c3cb6a7b45a8fae3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 20 Mar 2026 14:48:12 +0000 Subject: [PATCH 4/4] auto-update benchmark weights --- pallets/subtensor/src/macros/dispatches.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index f70b83f52d..8da8526ce0 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1247,7 +1247,7 @@ 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(36_u64)) + .saturating_add(T::DbWeight::get().reads(37_u64)) .saturating_add(T::DbWeight::get().writes(52_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network(origin: OriginFor, hotkey: T::AccountId) -> DispatchResult { Self::do_register_network(origin, &hotkey, 1, None) @@ -1456,7 +1456,7 @@ mod dispatches { /// User register a new subnetwork #[pallet::call_index(79)] #[pallet::weight((Weight::from_parts(396_000_000, 0) - .saturating_add(T::DbWeight::get().reads(35_u64)) + .saturating_add(T::DbWeight::get().reads(36_u64)) .saturating_add(T::DbWeight::get().writes(51_u64)), DispatchClass::Normal, Pays::Yes))] pub fn register_network_with_identity( origin: OriginFor,