Skip to content
Open
6 changes: 6 additions & 0 deletions chain-extensions/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ impl pallet_subtensor::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -463,6 +464,11 @@ impl CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
Expand Down
6 changes: 6 additions & 0 deletions eco-tests/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ impl pallet_subtensor::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -346,6 +347,11 @@ impl CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
Expand Down
6 changes: 6 additions & 0 deletions pallets/admin-utils/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ impl pallet_subtensor::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -366,6 +367,11 @@ impl pallet_subtensor::CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl pallet_subtensor::PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct GrandpaInterfaceImpl;
impl crate::GrandpaInterface<Test> for GrandpaInterfaceImpl {
fn schedule_change(
Expand Down
82 changes: 80 additions & 2 deletions pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// DEALINGS IN THE SOFTWARE.

use super::*;
use crate::CommitmentsInterface;
use crate::{CommitmentsInterface, PrecompileCleanupInterface};
use safe_math::*;
use substrate_fixed::types::{I64F64, U96F32};
use subtensor_runtime_common::{AlphaBalance, NetUid, NetUidStorageIndex, TaoBalance, Token};
Expand Down Expand Up @@ -218,6 +218,7 @@ impl<T: Config> Pallet<T> {
Self::destroy_alpha_in_out_stakes(netuid)?;
T::SwapInterface::clear_protocol_liquidity(netuid)?;
T::CommitmentsInterface::purge_netuid(netuid);
T::PrecompileCleanupInterface::purge_netuid(netuid);

// --- Remove the network
Self::remove_network(netuid);
Expand Down Expand Up @@ -273,18 +274,27 @@ impl<T: Config> Pallet<T> {
ValidatorPermit::<T>::remove(netuid);
ValidatorTrust::<T>::remove(netuid);

// Strip IsNetworkMember for every hotkey on this subnet and retain a
// snapshot for the orphan-hotkey cleanup pass further down.
let mut subnet_hotkeys: Vec<T::AccountId> = Vec::with_capacity(keys.len());
for (_uid, key) in keys {
IsNetworkMember::<T>::remove(key, netuid);
IsNetworkMember::<T>::remove(&key, netuid);
subnet_hotkeys.push(key);
}

// --- 10. Erase network parameters.
Tempo::<T>::remove(netuid);
Kappa::<T>::remove(netuid);
Difficulty::<T>::remove(netuid);
MaxAllowedUids::<T>::remove(netuid);
MinAllowedUids::<T>::remove(netuid);
ImmunityPeriod::<T>::remove(netuid);
ActivityCutoff::<T>::remove(netuid);
MinAllowedWeights::<T>::remove(netuid);
MaxWeightsLimit::<T>::remove(netuid);
AdjustmentAlpha::<T>::remove(netuid);
AdjustmentInterval::<T>::remove(netuid);
MinNonImmuneUids::<T>::remove(netuid);
RegistrationsThisInterval::<T>::remove(netuid);
POWRegistrationsThisInterval::<T>::remove(netuid);
BurnRegistrationsThisInterval::<T>::remove(netuid);
Expand All @@ -300,6 +310,11 @@ impl<T: Config> Pallet<T> {
SubnetEmaTaoFlow::<T>::remove(netuid);
SubnetTaoProvided::<T>::remove(netuid);

// --- 12. Root / emission split parameters.
RootProp::<T>::remove(netuid);
RecycleOrBurn::<T>::remove(netuid);
RootClaimableThreshold::<T>::remove(netuid);

// --- 13. Token / mechanism / registration toggles.
TokenSymbol::<T>::remove(netuid);
SubnetMechanism::<T>::remove(netuid);
Expand Down Expand Up @@ -362,12 +377,75 @@ impl<T: Config> Pallet<T> {
StakeWeight::<T>::remove(netuid);
LoadedEmission::<T>::remove(netuid);

// --- 18b. Voting power.
let _ = VotingPower::<T>::clear_prefix(netuid, u32::MAX, None);
VotingPowerTrackingEnabled::<T>::remove(netuid);
VotingPowerDisableAtBlock::<T>::remove(netuid);
VotingPowerEmaAlpha::<T>::remove(netuid);

// --- 18c. RootClaimable: outer key is hotkey, but the value is a
// BTreeMap<NetUid, _>. Strip this netuid from every entry and drop
// entries that become empty so no per-netuid state leaks past dereg.
// Note: finalize_all_subnet_root_dividends may have already stripped
// the netuid via mutate, leaving empty BTreeMaps — handle both cases.
let rc_hotkeys: sp_std::vec::Vec<T::AccountId> =
RootClaimable::<T>::iter().map(|(hot, _)| hot).collect();
for hot in rc_hotkeys {
let mut claimable = RootClaimable::<T>::get(&hot);
let had_netuid = claimable.remove(&netuid).is_some();
if claimable.is_empty() {
RootClaimable::<T>::remove(&hot);
} else if had_netuid {
RootClaimable::<T>::insert(&hot, claimable);
}
}

// --- 18d. Hotkey orphan cleanup. Any hotkey that was registered on
// this subnet and has now lost its last IsNetworkMember entry is no
// longer reachable via any subnet, so drop its per-hotkey global
// bookkeeping (Owner, Delegates, OwnedHotkeys, StakingHotkeys,
// LastColdkeyHotkeyStakeBlock) to stop state piling up forever.
let mut orphans: sp_std::collections::btree_set::BTreeSet<T::AccountId> =
sp_std::collections::btree_set::BTreeSet::new();
for hot in &subnet_hotkeys {
if IsNetworkMember::<T>::iter_prefix(hot).next().is_none() {
orphans.insert(hot.clone());
}
}
for hot in &orphans {
let cold = Owner::<T>::get(hot);
Owner::<T>::remove(hot);
Delegates::<T>::remove(hot);
OwnedHotkeys::<T>::mutate(&cold, |v| v.retain(|h| h != hot));
if OwnedHotkeys::<T>::get(&cold).is_empty() {
OwnedHotkeys::<T>::remove(&cold);
}
}
// StakingHotkeys and LastColdkeyHotkeyStakeBlock are keyed by coldkey;
// scan all coldkeys that staked anywhere and strip references to any
// orphan hotkey.
if !orphans.is_empty() {
let staking_colds: Vec<T::AccountId> =
StakingHotkeys::<T>::iter().map(|(c, _)| c).collect();
for cold in staking_colds {
StakingHotkeys::<T>::mutate(&cold, |v| v.retain(|h| !orphans.contains(h)));
if StakingHotkeys::<T>::get(&cold).is_empty() {
StakingHotkeys::<T>::remove(&cold);
}
for hot in &orphans {
LastColdkeyHotkeyStakeBlock::<T>::remove(&cold, hot);
}
}
}

// --- 19. DMAPs where netuid is the FIRST key: clear by prefix.
let _ = BlockAtRegistration::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = Axons::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = NeuronCertificates::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = Prometheus::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = AlphaDividendsPerSubnet::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = RootAlphaDividendsPerSubnet::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = RootClaimed::<T>::clear_prefix((netuid,), u32::MAX, None);
let _ = PendingChildKeys::<T>::clear_prefix(netuid, u32::MAX, None);
let _ = AssociatedEvmAddress::<T>::clear_prefix(netuid, u32::MAX, None);

Expand Down
5 changes: 5 additions & 0 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2780,3 +2780,8 @@ impl<T> ProxyInterface<T> for () {
pub trait CommitmentsInterface {
fn purge_netuid(netuid: NetUid);
}

/// EVM precompile crates implement this to clean up storage when a subnet is removed.
pub trait PrecompileCleanupInterface {
fn purge_netuid(netuid: NetUid);
}
5 changes: 4 additions & 1 deletion pallets/subtensor/src/macros/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use frame_support::pallet_macros::pallet_section;
#[pallet_section]
mod config {

use crate::{CommitmentsInterface, GetAlphaForTao, GetTaoForAlpha};
use crate::{CommitmentsInterface, GetAlphaForTao, GetTaoForAlpha, PrecompileCleanupInterface};
use pallet_commitments::GetCommitments;
use subtensor_runtime_common::AuthorshipInfo;
use subtensor_swap_interface::{SwapEngine, SwapHandler};
Expand Down Expand Up @@ -60,6 +60,9 @@ mod config {
/// Interface to clean commitments on network dissolution.
type CommitmentsInterface: CommitmentsInterface;

/// Interface to clean EVM precompile storage on network dissolution.
type PrecompileCleanupInterface: PrecompileCleanupInterface;

/// Rate limit for associating an EVM key.
type EvmKeyAssociateRateLimit: Get<u64>;

Expand Down
6 changes: 6 additions & 0 deletions pallets/subtensor/src/tests/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ impl crate::Config for Test {
type GetCommitments = ();
type MaxImmuneUidsPercentage = MaxImmuneUidsPercentage;
type CommitmentsInterface = CommitmentsI;
type PrecompileCleanupInterface = PrecompileCleanupI;
type EvmKeyAssociateRateLimit = EvmKeyAssociateRateLimit;
type AuthorshipProvider = MockAuthorshipProvider;
type WeightInfo = ();
Expand Down Expand Up @@ -361,6 +362,11 @@ impl CommitmentsInterface for CommitmentsI {
fn purge_netuid(_netuid: NetUid) {}
}

pub struct PrecompileCleanupI;
impl PrecompileCleanupInterface for PrecompileCleanupI {
fn purge_netuid(_netuid: NetUid) {}
}

parameter_types! {
pub MaximumSchedulerWeight: Weight = Perbill::from_percent(80) *
BlockWeights::get().max_block;
Expand Down
Loading