Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 25 additions & 44 deletions pallets/subtensor/src/staking/set_children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,66 +600,47 @@ impl<T: Config> Pallet<T> {
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::<T>::get();
let cooldown_allowed_block =
if let Some(first_emission_block) = FirstEmissionBlockNumber::<T>::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::<T>::get(netuid);
Comment on lines -603 to +622
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a function that allows one to check if and what the cooldown would be, so that we can test it properly, expose it to a a smart contract via a precompile etc, otherwise they will have to reimplement this method line by line in a smart contract

Copy link
Copy Markdown
Contributor Author

@gztensor gztensor Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the new change (applying immediately) reading cooldown is simple:

Start call  |-- yes --> 1 day
            |-- no  --> 0


// Iterate over all pending children of this subnet and set as needed
let mut to_remove: Vec<T::AccountId> = Vec::new();
let mut to_reschedule: Vec<(T::AccountId, Vec<(u64, T::AccountId)>)> = Vec::new();

PendingChildKeys::<T>::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);
}
},
);

for hotkey in to_remove {
PendingChildKeys::<T>::remove(netuid, hotkey);
}

for (hotkey, children) in to_reschedule {
PendingChildKeys::<T>::insert(netuid, hotkey, (children, cooldown_allowed_block));
}
}

/* Retrieves the list of children for a given hotkey and network.
Expand Down
47 changes: 47 additions & 0 deletions pallets/subtensor/src/tests/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4220,3 +4220,50 @@ fn test_set_child_keys_empty_vector_clears_storage() {
assert!(ParentKeys::<Test>::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::<Test>::contains_key(netuid, hotkey));

// Clear SubtokenEnabled
SubtokenEnabled::<Test>::remove(netuid);

// Normally happens on epoch
SubtensorModule::do_set_pending_children(netuid);

// Verify pending map is empty
assert!(!PendingChildKeys::<Test>::contains_key(netuid, hotkey));

// Verify that childkey is set
assert_eq!(
ChildKeys::<Test>::get(hotkey, netuid),
vec![(proportion1, child1), (proportion2, child2)]
);
});
}
Loading