diff --git a/pallets/subtensor/src/macros/hooks.rs b/pallets/subtensor/src/macros/hooks.rs index ed0b46314b..7f7320ce2c 100644 --- a/pallets/subtensor/src/macros/hooks.rs +++ b/pallets/subtensor/src/macros/hooks.rs @@ -179,7 +179,9 @@ mod hooks { // Fix lock state left behind by subnet-scoped hotkey swaps. .saturating_add(migrations::migrate_fix_subnet_hotkey_lock_swaps::migrate_fix_subnet_hotkey_lock_swaps::()) // Populate reverse lookup index for EVM address associations. - .saturating_add(migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::()); + .saturating_add(migrations::migrate_associated_evm_address_index::migrate_associated_evm_address_index::()) + // Remove orphan SubnetIdentitiesV3 entries left for recycled netuids. + .saturating_add(migrations::migrate_clear_orphan_subnet_identities_v3::migrate_clear_orphan_subnet_identities_v3::()); weight } diff --git a/pallets/subtensor/src/migrations/migrate_clear_orphan_subnet_identities_v3.rs b/pallets/subtensor/src/migrations/migrate_clear_orphan_subnet_identities_v3.rs new file mode 100644 index 0000000000..b024daca47 --- /dev/null +++ b/pallets/subtensor/src/migrations/migrate_clear_orphan_subnet_identities_v3.rs @@ -0,0 +1,63 @@ +use crate::{Config, Event, HasMigrationRun, NetworksAdded, Pallet, SubnetIdentitiesV3, Weight}; +use frame_support::traits::Get; +use scale_info::prelude::string::String; +use sp_std::vec::Vec; +use subtensor_runtime_common::NetUid; + +/// Remove `SubnetIdentitiesV3` entries that belong to netuids which are no +/// longer registered networks (`!NetworksAdded`). +/// +/// Such orphan identities accumulate when a subnet slot is recycled: a new +/// owner registers a subnet in a previously-used netuid without supplying an +/// identity, and the stale identity from the prior owner is left in place, +/// misleading participants about what the subnet is. The registration path +/// (`set_new_network_state`) only writes on `Some(identity)` and never clears a +/// pre-existing entry, and the historical `migrate_subnet_identities_to_v3` +/// migration copied V2 entries unconditionally. +/// +/// This mirrors the identity-clear in `do_dissolve_network` as a one-shot +/// upgrade migration: orphan entries are removed and a `SubnetIdentityRemoved` +/// event is emitted for each; identities of live subnets are untouched. +pub fn migrate_clear_orphan_subnet_identities_v3() -> Weight { + let migration_name = b"migrate_clear_orphan_subnet_identities_v3".to_vec(); + let mut weight = T::DbWeight::get().reads(1); + + if HasMigrationRun::::get(&migration_name) { + log::info!( + target: "runtime", + "Migration '{}' already run - skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + log::info!( + target: "runtime", + "Running migration '{}'", + String::from_utf8_lossy(&migration_name), + ); + + // Collect identity netuids that no longer correspond to a registered subnet. + let orphan_netuids: Vec = SubnetIdentitiesV3::::iter_keys() + .filter(|netuid| !NetworksAdded::::get(netuid)) + .collect(); + let removed = orphan_netuids.len() as u64; + weight = weight.saturating_add(T::DbWeight::get().reads(removed)); + + for netuid in orphan_netuids { + SubnetIdentitiesV3::::remove(netuid); + Pallet::::deposit_event(Event::SubnetIdentityRemoved(netuid)); + } + weight = weight.saturating_add(T::DbWeight::get().writes(removed)); + + HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + + log::info!( + target: "runtime", + "Migration '{}' completed.", + String::from_utf8_lossy(&migration_name), + ); + + weight +} diff --git a/pallets/subtensor/src/migrations/mod.rs b/pallets/subtensor/src/migrations/mod.rs index 98699ccbce..ff9829ec1f 100644 --- a/pallets/subtensor/src/migrations/mod.rs +++ b/pallets/subtensor/src/migrations/mod.rs @@ -8,6 +8,7 @@ pub mod migrate_associated_evm_address_index; pub mod migrate_auto_stake_destination; pub mod migrate_cleanup_swap_v3; pub mod migrate_clear_deprecated_registration_maps; +pub mod migrate_clear_orphan_subnet_identities_v3; pub mod migrate_coldkey_swap_scheduled; pub mod migrate_coldkey_swap_scheduled_to_announcements; pub mod migrate_commit_reveal_settings; diff --git a/pallets/subtensor/src/tests/migration.rs b/pallets/subtensor/src/tests/migration.rs index 8e81447fbc..183a409d52 100644 --- a/pallets/subtensor/src/tests/migration.rs +++ b/pallets/subtensor/src/tests/migration.rs @@ -78,6 +78,51 @@ fn test_migrate_associated_evm_address_index() { }); } +#[test] +fn test_migrate_clear_orphan_subnet_identities_v3() { + new_test_ext(1).execute_with(|| { + let migration_name = b"migrate_clear_orphan_subnet_identities_v3".to_vec(); + HasMigrationRun::::remove(&migration_name); + + let orphan_netuid = NetUid::from(1); + let live_netuid = NetUid::from(2); + + // live_netuid is a registered network; orphan_netuid is not. + NetworksAdded::::insert(live_netuid, true); + + let orphan_identity = SubnetIdentityV3 { + subnet_name: b"orphan".to_vec(), + ..Default::default() + }; + let live_identity = SubnetIdentityV3 { + subnet_name: b"live".to_vec(), + ..Default::default() + }; + + SubnetIdentitiesV3::::insert(orphan_netuid, orphan_identity); + SubnetIdentitiesV3::::insert(live_netuid, live_identity.clone()); + + crate::migrations::migrate_clear_orphan_subnet_identities_v3::migrate_clear_orphan_subnet_identities_v3::(); + + // The orphan identity is removed; the live subnet identity is preserved. + assert!(!SubnetIdentitiesV3::::contains_key(orphan_netuid)); + assert_eq!( + SubnetIdentitiesV3::::get(live_netuid), + Some(live_identity.clone()) + ); + + // Migration is marked as run. + assert!(HasMigrationRun::::get(&migration_name)); + + // Idempotent: re-running is a no-op (live identity still present). + crate::migrations::migrate_clear_orphan_subnet_identities_v3::migrate_clear_orphan_subnet_identities_v3::(); + assert_eq!( + SubnetIdentitiesV3::::get(live_netuid), + Some(live_identity) + ); + }); +} + #[test] fn test_migrate_associated_evm_address_index_reconciles_over_cap_buckets() { new_test_ext(1).execute_with(|| { @@ -1254,6 +1299,7 @@ fn test_per_u16_encodes_identically_to_u16() { } #[test] +#[allow(deprecated)] fn test_migrate_last_tx_block_delegate_take() { new_test_ext(1).execute_with(|| { // ------------------------------ diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 017a71a855..3ab1e61284 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -235,7 +235,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 429, + spec_version: 432, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1,