Skip to content
4 changes: 2 additions & 2 deletions pallets/subtensor/src/macros/dispatches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>, hotkey: T::AccountId) -> DispatchResult {
Self::do_register_network(origin, &hotkey, 1, None)
Expand Down Expand Up @@ -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<T>,
Expand Down
61 changes: 61 additions & 0 deletions pallets/subtensor/src/staking/set_children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,67 @@ impl<T: Config> Pallet<T> {
// 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::<T>::RegistrationNotPermittedOnRootSubnet
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

need a different error type.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Seems close enough. Also, I don't expect errors here at all because it's called only during subnet registration with concrete netuid. What do you suggest instead?

);

// Subnet must exist.
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);

// Get the subnet owner hotkey.
let subnet_owner_hotkey =
SubnetOwnerHotkey::<T>::try_get(netuid).map_err(|_| Error::<T>::SubnetNotExists)?;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

need a different error type.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Do we have a better one? It seems appropriate. We don't have subnet owner hotkey only when it doesn't exist. What do you suggest?


// Iterate over all root validators and schedule each one as a parent
// of the subnet owner hotkey.
for (_uid, root_validator_hotkey) in Keys::<T>::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 as frame_system::Config>::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();
Expand Down
11 changes: 10 additions & 1 deletion pallets/subtensor/src/subnets/subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,16 @@ impl<T: Config> Pallet<T> {
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));

Expand Down
273 changes: 273 additions & 0 deletions pallets/subtensor/src/tests/children.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4220,3 +4220,276 @@ fn test_set_child_keys_empty_vector_clears_storage() {
assert!(ParentKeys::<Test>::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<u16> = vec![0];
let values: Vec<u16> = 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::<Test>::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
));
});
}

// 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::<Test>::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::<Test>::get().saturating_sub(1)).into();
assert_eq!(
SubnetOwnerHotkey::<Test>::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::<Test>::contains_key(netuid, root_val_hotkey_1),
"Root validator 1 should have pending children on the new subnet"
);
assert!(
PendingChildKeys::<Test>::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
));
});
}
Loading