From eacdd85fed5591dd556c0d38182abaaf062892de Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 31 Mar 2026 17:33:46 -0400 Subject: [PATCH 1/4] Set CK immediately if start call wasn't executed --- pallets/subtensor/src/staking/set_children.rs | 69 +++++++------------ pallets/subtensor/src/tests/children.rs | 47 +++++++++++++ 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 6aa537e4f7..980c5e685f 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -600,55 +600,40 @@ impl Pallet { let current_block = Self::get_current_block_as_u64(); // If the childkey cools down before the subnet start call + PendingChildKeyCooldown: - // - If Start call happened: Postpone to start_block + PendingChildKeyCooldown - // - If Start call didn't happen: Postpone to now + PendingChildKeyCooldown - let cooldown_period = PendingChildKeyCooldown::::get(); - let cooldown_allowed_block = - if let Some(first_emission_block) = FirstEmissionBlockNumber::::get(netuid) { - let start_call_block = first_emission_block.saturating_sub(1); - start_call_block.saturating_add(cooldown_period) - } else { - current_block.saturating_add(cooldown_period) - }; + // - If Start call happened: Normal track + // - If Start call didn't happen: Apply immediately + let start_call_occured = SubtokenEnabled::::get(netuid) == true; // Iterate over all pending children of this subnet and set as needed let mut to_remove: Vec = Vec::new(); - let mut to_reschedule: Vec<(T::AccountId, Vec<(u64, T::AccountId)>)> = Vec::new(); PendingChildKeys::::iter_prefix(netuid).for_each( |(hotkey, (children, cool_down_block))| { - if cool_down_block < current_block { - if current_block >= cooldown_allowed_block { - // If child-parent consistency is broken, we will fail setting new children silently - let maybe_relations = - Self::load_relations_from_pending(hotkey.clone(), &children, netuid); - if let Ok(relations) = maybe_relations { - let mut _weight: Weight = T::DbWeight::get().reads(0); - if let Ok(()) = Self::persist_child_parent_relations( - relations, + if (cool_down_block < current_block) || !start_call_occured { + // If child-parent consistency is broken, we will fail setting new children silently + let maybe_relations = + Self::load_relations_from_pending(hotkey.clone(), &children, netuid); + if let Ok(relations) = maybe_relations { + let mut _weight: Weight = T::DbWeight::get().reads(0); + if let Ok(()) = + Self::persist_child_parent_relations(relations, netuid, &mut _weight) + { + // Log and emit event. + log::trace!( + "SetChildren( netuid:{:?}, hotkey:{:?}, children:{:?} )", + hotkey, netuid, - &mut _weight, - ) { - // Log and emit event. - log::trace!( - "SetChildren( netuid:{:?}, hotkey:{:?}, children:{:?} )", - hotkey, - netuid, - children.clone() - ); - Self::deposit_event(Event::SetChildren( - hotkey.clone(), - netuid, - children.clone(), - )); - } + children.clone() + ); + Self::deposit_event(Event::SetChildren( + hotkey.clone(), + netuid, + children.clone(), + )); } - - to_remove.push(hotkey); - } else { - to_remove.push(hotkey.clone()); - to_reschedule.push((hotkey, children)); } + + to_remove.push(hotkey); } }, ); @@ -656,10 +641,6 @@ impl Pallet { for hotkey in to_remove { PendingChildKeys::::remove(netuid, hotkey); } - - for (hotkey, children) in to_reschedule { - PendingChildKeys::::insert(netuid, hotkey, (children, cooldown_allowed_block)); - } } /* Retrieves the list of children for a given hotkey and network. diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 1558c4cb6c..d81e7265c7 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -4220,3 +4220,50 @@ fn test_set_child_keys_empty_vector_clears_storage() { assert!(ParentKeys::::get(child, netuid).is_empty()); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --package pallet-subtensor --lib -- tests::children::test_set_child_keys_no_start_call_sets_immediately --exact --show-output +#[test] +fn test_set_child_keys_no_start_call_sets_immediately() { + new_test_ext(1).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(2); + let child1 = U256::from(3); + let child2 = U256::from(4); + let netuid = NetUid::from(1); + let proportion1: u64 = 1000; + let proportion2: u64 = 2000; + + // Add network and register hotkey + add_network(netuid, 13, 0); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + // Set multiple children + mock_schedule_children( + &coldkey, + &hotkey, + netuid, + &[(proportion1, child1), (proportion2, child2)], + ); + + // Normally happens on epoch + SubtensorModule::do_set_pending_children(netuid); + + // Verify pending map contains our parent + assert!(PendingChildKeys::::contains_key(netuid, hotkey)); + + // Clear SubtokenEnabled + SubtokenEnabled::::remove(netuid); + + // Normally happens on epoch + SubtensorModule::do_set_pending_children(netuid); + + // Verify pending map is empty + assert!(!PendingChildKeys::::contains_key(netuid, hotkey)); + + // Verify that childkey is set + assert_eq!( + ChildKeys::::get(hotkey, netuid), + vec![(proportion1, child1), (proportion2, child2)] + ); + }); +} From 98174b5f0a7f8cf67bb80431c47c436abc821464 Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Tue, 31 Mar 2026 17:35:46 -0400 Subject: [PATCH 2/4] Fix clippy --- pallets/subtensor/src/staking/set_children.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 980c5e685f..8a498f45a4 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -602,7 +602,7 @@ impl Pallet { // If the childkey cools down before the subnet start call + PendingChildKeyCooldown: // - If Start call happened: Normal track // - If Start call didn't happen: Apply immediately - let start_call_occured = SubtokenEnabled::::get(netuid) == true; + let start_call_occured = SubtokenEnabled::::get(netuid); // Iterate over all pending children of this subnet and set as needed let mut to_remove: Vec = Vec::new(); From e9bfd3fbf491ebed7ba623964fff321e570096ab Mon Sep 17 00:00:00 2001 From: Greg Zaitsev Date: Wed, 1 Apr 2026 10:10:18 -0400 Subject: [PATCH 3/4] Apply ck immediately without scheduling before the start call --- pallets/subtensor/src/staking/set_children.rs | 69 ++++++++++++------- pallets/subtensor/src/tests/children.rs | 13 ++-- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index 8a498f45a4..90b1790f6d 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -547,6 +547,22 @@ impl Pallet { let current_block = Self::get_current_block_as_u64(); TransactionType::SetChildren.set_last_block_on_subnet::(&hotkey, netuid, current_block); + // Schedule or immediately apply CK + Self::schedule_or_apply_ck(netuid, hotkey, children) + } + + /// If the start call occured, schedule children, otherwise, + /// apply immediately + fn schedule_or_apply_ck( + netuid: NetUid, + hotkey: T::AccountId, + children: Vec<(u64, T::AccountId)>, + ) -> DispatchResult { + if !SubtokenEnabled::::get(netuid) { + Self::persist_pending_chidren_ok(netuid, &hotkey, &children); + return Ok(()); + } + // Calculate cool-down block let cooldown_block = Self::get_current_block_as_u64().saturating_add(PendingChildKeyCooldown::::get()); @@ -554,7 +570,7 @@ impl Pallet { // Insert or update PendingChildKeys PendingChildKeys::::insert(netuid, hotkey.clone(), (children.clone(), cooldown_block)); - // --- 8. Log and return. + // Log and return. log::trace!( "SetChildrenScheduled( netuid:{:?}, cooldown_block:{:?}, hotkey:{:?}, children:{:?} )", cooldown_block, @@ -563,10 +579,10 @@ impl Pallet { children.clone() ); Self::deposit_event(Event::SetChildrenScheduled( - hotkey.clone(), + hotkey, netuid, cooldown_block, - children.clone(), + children, )); // Ok and return. @@ -602,6 +618,7 @@ impl Pallet { // If the childkey cools down before the subnet start call + PendingChildKeyCooldown: // - If Start call happened: Normal track // - If Start call didn't happen: Apply immediately + // TODO: This check may be removed after all ck are applied after the runtime upgrade let start_call_occured = SubtokenEnabled::::get(netuid); // Iterate over all pending children of this subnet and set as needed @@ -610,29 +627,7 @@ impl Pallet { PendingChildKeys::::iter_prefix(netuid).for_each( |(hotkey, (children, cool_down_block))| { if (cool_down_block < current_block) || !start_call_occured { - // If child-parent consistency is broken, we will fail setting new children silently - let maybe_relations = - Self::load_relations_from_pending(hotkey.clone(), &children, netuid); - if let Ok(relations) = maybe_relations { - let mut _weight: Weight = T::DbWeight::get().reads(0); - if let Ok(()) = - Self::persist_child_parent_relations(relations, netuid, &mut _weight) - { - // Log and emit event. - log::trace!( - "SetChildren( netuid:{:?}, hotkey:{:?}, children:{:?} )", - hotkey, - netuid, - children.clone() - ); - Self::deposit_event(Event::SetChildren( - hotkey.clone(), - netuid, - children.clone(), - )); - } - } - + Self::persist_pending_chidren_ok(netuid, &hotkey, &children); to_remove.push(hotkey); } }, @@ -643,6 +638,28 @@ impl Pallet { } } + // If child-parent consistency is broken, fail setting new children silently + fn persist_pending_chidren_ok( + netuid: NetUid, + hotkey: &T::AccountId, + children: &Vec<(u64, T::AccountId)>, + ) { + let maybe_relations = Self::load_relations_from_pending(hotkey.clone(), children, netuid); + if let Ok(relations) = maybe_relations { + let mut _weight: Weight = T::DbWeight::get().reads(0); + if let Ok(()) = Self::persist_child_parent_relations(relations, netuid, &mut _weight) { + // Log and emit event. + log::trace!( + "SetChildren( netuid:{:?}, hotkey:{:?}, children:{:?} )", + hotkey, + netuid, + children.clone() + ); + Self::deposit_event(Event::SetChildren(hotkey.clone(), netuid, children.clone())); + } + } + } + /* Retrieves the list of children for a given hotkey and network. /// /// # Arguments diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index d81e7265c7..2f4bdb537a 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -2234,6 +2234,7 @@ fn test_do_remove_stake_clears_pending_childkeys() { add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); SubtensorModule::add_balance_to_coldkey_account(&coldkey, 10_000_000_000_000_u64.into()); + SubtokenEnabled::::insert(netuid, true); let reserve = 1_000_000_000_000_000_u64; mock::setup_reserves(netuid, reserve.into(), reserve.into()); @@ -4237,6 +4238,9 @@ fn test_set_child_keys_no_start_call_sets_immediately() { add_network(netuid, 13, 0); register_ok_neuron(netuid, hotkey, coldkey, 0); + // Clear SubtokenEnabled + SubtokenEnabled::::remove(netuid); + // Set multiple children mock_schedule_children( &coldkey, @@ -4248,15 +4252,6 @@ fn test_set_child_keys_no_start_call_sets_immediately() { // Normally happens on epoch SubtensorModule::do_set_pending_children(netuid); - // Verify pending map contains our parent - assert!(PendingChildKeys::::contains_key(netuid, hotkey)); - - // Clear SubtokenEnabled - SubtokenEnabled::::remove(netuid); - - // Normally happens on epoch - SubtensorModule::do_set_pending_children(netuid); - // Verify pending map is empty assert!(!PendingChildKeys::::contains_key(netuid, hotkey)); From 5af8bee3c63956a47afd50d8f4619441f7571ae9 Mon Sep 17 00:00:00 2001 From: Sam Johnson Date: Thu, 2 Apr 2026 18:50:37 -0400 Subject: [PATCH 4/4] fix CI --- pallets/subtensor/src/tests/children.rs | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 6989bd5612..ef721b671b 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -4391,8 +4391,9 @@ 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. +// Test that register_network automatically sets root validators as parents of the +// subnet owner, enabling the owner to set weights. Since SubtokenEnabled is false +// for a new subnet (start_call hasn't executed yet), child keys are applied immediately. // 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() { @@ -4481,21 +4482,7 @@ fn test_register_network_schedules_root_validators() { 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 --- + // --- Verify child keys were applied immediately (SubtokenEnabled is false for new subnets) --- let children_1 = SubtensorModule::get_children(&root_val_hotkey_1, netuid); assert_eq!( children_1,