feat(subtensor): reset subnet identity on subnet create#2601
feat(subtensor): reset subnet identity on subnet create#2601ArtificialXai wants to merge 1 commit intoopentensor:mainfrom
Conversation
|
can you identity the "Storage migrations and legacy code paths can leave an orphan SubnetIdentitiesV3 entry". then we fix it, instead of add the check in registration. |
Closes opentensor#2572. `do_register_network` previously only *set* a `SubnetIdentityV3` entry when the caller passed `Some(identity)` and left the storage map untouched otherwise. Storage migrations and legacy code paths can leave an orphan `SubnetIdentitiesV3` entry on a netuid slot that later gets assigned to a new owner; that orphan then leaks into the fresh subnet and misrepresents what the subnet is about. This change makes the `identity` argument authoritative: * `Some(identity)` — validate and insert (existing behavior). * `None` — if an entry already exists for the target netuid, remove it and emit `SubnetIdentityRemoved`; no-op if the slot is already empty. Three regression tests in `tests::networks` cover the three branches.
8e169dd to
fd2be3f
Compare
|
Signed and re-pushed ( |
|
@open-junius Thanks for pushing back — chased it down, and the orphan source is Introduced in let old_subnet_identities = SubnetIdentitiesV2::<T>::iter().collect::<Vec<_>>();
for (netuid, old_subnet_identity) in old_subnet_identities.clone() {
let new_subnet_identity = SubnetIdentityV3 { /* copy fields, blanks for new ones */ };
SubnetIdentitiesV3::<T>::insert(netuid, &new_subnet_identity);
SubnetIdentitiesV2::<T>::remove(netuid);
}The loop copies every V2 had the same hazard before it: older dissolve paths didn't scrub
Given this, the surgical source-level fix you're asking for is a one-shot cleanup migration that iterates pub fn migrate_clear_orphan_subnet_identities_v3<T: Config>() -> Weight {
let migration_name = b"migrate_clear_orphan_subnet_identities_v3".to_vec();
let mut weight = T::DbWeight::get().reads(1);
if HasMigrationRun::<T>::get(&migration_name) { return weight; }
let netuids: Vec<_> = SubnetIdentitiesV3::<T>::iter_keys().collect();
for netuid in netuids {
weight = weight.saturating_add(T::DbWeight::get().reads(1));
if !NetworksAdded::<T>::get(netuid) {
SubnetIdentitiesV3::<T>::remove(netuid);
Pallet::<T>::deposit_event(Event::SubnetIdentityRemoved(netuid));
weight = weight.saturating_add(T::DbWeight::get().writes(1));
}
}
HasMigrationRun::<T>::insert(&migration_name, true);
weight.saturating_add(T::DbWeight::get().writes(1))
}Happy to fold that migration into this PR and drop the |
Summary
Closes #2572.
do_register_networkpreviously only set aSubnetIdentityV3entry when the caller passedSome(identity)and left the storage map untouched otherwise. Storage migrations and legacy code paths can leave an orphanSubnetIdentitiesV3entry on a netuid slot that later gets assigned to a new owner; that orphan then leaks into the fresh subnet and misrepresents what the subnet is about (wrong name, wrong GitHub repo, wrong Discord, etc.).This PR makes the
identityargument authoritative, matching the natural intuition of "the caller ofregister_networkfully owns the resulting subnet's presentation":Some(identity)— validate and insert (existing behavior, unchanged).None— if an entry already exists for the target netuid, remove it and emitSubnetIdentityRemoved; no-op if the slot is already empty.No runtime migration required — the fix is forward-only and only kicks in when
do_register_networkis called, so existing subnets are unaffected.Changes
pallets/subtensor/src/subnets/subnet.rs— step 17 ofdo_register_networknow uses amatch identityinstead ofif let Some(..), with aNonebranch that clears any staleSubnetIdentitiesV3entry and emits the existingSubnetIdentityRemovedevent (already used bydo_dissolve_networkincoinbase/root.rs).pallets/subtensor/src/tests/networks.rs— three regression tests:register_network_with_none_identity_clears_stale_entry— pre-inserts a staleSubnetIdentityV3at the predicted next netuid, callsdo_register_network(..., None), asserts the entry is gone.register_network_with_some_identity_overwrites_stale_entry— same setup but passesSome(new_identity), asserts the stored entry equalsnew_identity.register_network_with_none_identity_no_op_when_slot_empty— registers withNoneagainst an empty slot, asserts noSubnetIdentitiesV3entry is created.Test plan
cargo check -p pallet-subtensorcargo test -p pallet-subtensor --features pow-faucet -- tests::networks::register_network_with_none_identity_clears_stale_entrycargo test -p pallet-subtensor --features pow-faucet -- tests::networks::register_network_with_some_identity_overwrites_stale_entrycargo test -p pallet-subtensor --features pow-faucet -- tests::networks::register_network_with_none_identity_no_op_when_slot_emptycargo test -p pallet-subtensor(CI)Notes
SubnetIdentityRemovedevent is already emitted bydo_dissolve_networkwhen a subnet is torn down, so consumers that index events should already handle it gracefully.None-branch behavior only removes stale state; it can never overwrite a caller's current subnet identity, since it runs insidedo_register_networkwhich creates the subnet.subnet_name/github_repo/discorduntil the new owner manually callsset_subnet_identity.