Skip to content

fix: clean up orphaned storage on subnet deregistration#2579

Open
plind-junior wants to merge 14 commits intoopentensor:devnet-readyfrom
plind-junior:fix/cleanup-root-alpha-dividends-on-network-removal
Open

fix: clean up orphaned storage on subnet deregistration#2579
plind-junior wants to merge 14 commits intoopentensor:devnet-readyfrom
plind-junior:fix/cleanup-root-alpha-dividends-on-network-removal

Conversation

@plind-junior
Copy link
Copy Markdown
Contributor

@plind-junior plind-junior commented Apr 9, 2026

Description

When a subnet is dissolved, its netuid gets recycled for future registrations. Several storage items were not being cleared during remove_network, which means a newly registered subnet could inherit stale state from the previous occupant of that netuid.

This PR ensures all per-subnet storage is properly cleaned up on deregistration:

  • EVM allowances: AllowancesStorage embeds netuid in its second key (spender, netuid), so it can't be cleared with clear_prefix. We introduce a PrecompileCleanupInterface trait (mirroring the existing CommitmentsInterface pattern) to let the pallet invoke cleanup across the crate boundary without creating a circular dependency.
  • Double maps (RootAlphaDividendsPerSubnet, VotingPower, RootClaimed): cleared via clear_prefix since netuid is the first key.
  • Simple maps that were previously missed: MinAllowedUids, MaxWeightsLimit, AdjustmentAlpha, AdjustmentInterval, MinNonImmuneUids, RootProp, RecycleOrBurn, RootClaimableThreshold, VotingPowerTrackingEnabled, VotingPowerDisableAtBlock, VotingPowerEmaAlpha.

Related Issue(s)

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Other (please describe):

Breaking Change

N/A — only adds cleanup that was previously missing. No behavioral change for live subnets.

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run ./scripts/fix_rust.sh to ensure my code is formatted and linted correctly
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Additional Notes

Subnet deregistration is infrequent, so iterating AllowancesStorage to filter by netuid is acceptable. The simple map removals are single-key deletes with negligible cost.

@plind-junior plind-junior force-pushed the fix/cleanup-root-alpha-dividends-on-network-removal branch from f2cbbd4 to a4261ae Compare April 9, 2026 06:54
@plind-junior plind-junior marked this pull request as ready for review April 9, 2026 06:57
@plind-junior plind-junior force-pushed the fix/cleanup-root-alpha-dividends-on-network-removal branch 2 times, most recently from 5f14e48 to 462b882 Compare April 9, 2026 07:32
RootAlphaDividendsPerSubnet is not cleaned up when a subnet is
dissolved. Since netuids are recycled (subnet.rs:178-186), stale root
dividend entries from the old subnet persist and become visible to the
new subnet, potentially affecting run_coinbase dividend calculations.

Add clear_prefix for RootAlphaDividendsPerSubnet alongside the existing
AlphaDividendsPerSubnet cleanup in remove_network.

Closes opentensor#1867
@plind-junior plind-junior force-pushed the fix/cleanup-root-alpha-dividends-on-network-removal branch from 462b882 to 7074c43 Compare April 9, 2026 07:43
@open-junius
Copy link
Copy Markdown
Contributor

pls fix the fmt in CI. and the generation make the solution complicated. subnet deregistration and registration not happens frequently. we can just remove the storage after subnet is deregistered.

Add PrecompileCleanupInterface trait to allow the pallet to invoke
cross-crate storage cleanup when a subnet is removed. The precompiles
crate iterates and removes all AllowancesStorage entries matching the
dissolved netuid, preventing stale EVM approvals from carrying over
when the netuid is recycled.
@plind-junior plind-junior force-pushed the fix/cleanup-root-alpha-dividends-on-network-removal branch from 7074c43 to 0010253 Compare April 9, 2026 13:03
Add PrecompileCleanupInterface no-op to all test mocks (chain-extensions,
eco-tests, admin-utils, transaction-fee). Clean up additional orphaned
storage items on subnet deregistration: MinAllowedUids, MaxWeightsLimit,
AdjustmentAlpha, AdjustmentInterval, MinNonImmuneUids, RootProp,
RecycleOrBurn, RootClaimableThreshold, VotingPower, VotingPowerTrackingEnabled,
VotingPowerDisableAtBlock, VotingPowerEmaAlpha. Move PrecompileCleanup
struct into the precompiles crate for cleaner wiring.
@plind-junior plind-junior changed the title fix: clear RootAlphaDividendsPerSubnet on network removal fix: clean up orphaned storage on subnet deregistration Apr 9, 2026
@open-junius open-junius added skip-cargo-audit This PR fails cargo audit but needs to be merged anyway labels Apr 9, 2026
@sam0x17 sam0x17 added the skip-validate-benchmarks This PR fails validate benchmarks but needs to be merged anyway label Apr 9, 2026
Alpha storage was migrated to AlphaV2 (share-based) in January. The test's
before hook was still calling api.query.SubtensorModule.Alpha.getValue,
which now returns 0 for new stake and caused the stakeAfter > stakeBefore
assertion to fail. Use contract.getStake on both sides instead, since
that's what the rest of the suite does and it exercises the precompile
directly.
@open-junius
Copy link
Copy Markdown
Contributor

The PR#2592 is merged. please pull devnet-ready and fix the conflict.

plind-junior and others added 4 commits April 20, 2026 20:23
…-root-alpha-dividends-on-network-removal

# Conflicts:
#	contract-tests/test/staking.precompile.approval.test.ts
#	pallets/subtensor/src/tests/networks.rs
RootClaimable is keyed by hotkey but stores BTreeMap<NetUid, _> as its
value, so a plain StorageMap walk misses the per-subnet state hiding
inside each entry. After dereg the stripped netuid could linger (and
empty BTreeMap entries could pile up) because finalize_all_subnet_root_
dividends uses mutate, which keeps zero-value entries under ValueQuery.

Add a cleanup step in remove_network that removes the dissolved netuid
from every hotkey's BTreeMap and drops the outer entry entirely when
the map becomes empty, regardless of whether this pass or the earlier
finalize call was the one to strip the netuid.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduces test_dissolve_network_no_storage_leak which automatically
detects per-subnet storage that is not cleaned up when a subnet is
dissolved. The test:

1. Snapshots ALL raw storage keys before subnet creation
2. Creates a subnet, registers neurons, stakes, serves axon/prometheus,
   sets childkeys, sets weights, and runs 2 epochs
3. Dissolves the subnet via root
4. Snapshots ALL storage keys after dissolution
5. Diffs the snapshots, filtering to SubtensorModule and Swap pallets,
   excluding known global storage items

This is future-proof: when a developer adds a new per-netuid StorageMap
but forgets cleanup in remove_network, this test fails automatically
with a clear error message naming the leaked storage item.

Also fixes Swap::ScrapReservoirTao and Swap::ScrapReservoirAlpha not
being cleaned up in do_clear_protocol_liquidity.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extend subnet dissolution cleanup to drop per-hotkey global bookkeeping
(Owner, Delegates, OwnedHotkeys, StakingHotkeys, LastColdkeyHotkeyStakeBlock)
for hotkeys that lose their last IsNetworkMember entry when the subnet
goes away. Without this, a hotkey registered only on a dissolved subnet
leaves orphan entries forever.

Tighten the leak-detection allowlist in the dissolution test to match:
drop Owner/Delegates/OwnedHotkeys/StakingHotkeys/RootClaimable/
LastColdkeyHotkeyStakeBlock/UsedWork/AlphaMapLastKey, and add
RegisteredSubnetCounter (the one intentional per-subnet counter).
Seed RootClaimable with a per-netuid entry in the test lifecycle so
the BTreeMap-hidden per-subnet state is exercised.

Deduplicate VotingPower::clear_prefix (was in both 18b and 19) and
RootProp/RootClaimableThreshold removals (already handled in section 12).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@open-junius
Copy link
Copy Markdown
Contributor

please fix the CI

ppolewicz
ppolewicz previously approved these changes Apr 24, 2026
Copy link
Copy Markdown
Collaborator

@ppolewicz ppolewicz left a comment

Choose a reason for hiding this comment

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

it looks like a good superset of my PR

Storage key was extended to (H160, u16, u64) in eea8d41 but
purge_netuid_allowances and its unit test still used the old 2-tuple
shape, breaking every compile-dependent CI check. Rewire both to the
3-tuple key. Drop stray blank line in networks.rs flagged by rustfmt.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@sam0x17
Copy link
Copy Markdown
Contributor

sam0x17 commented Apr 24, 2026

merge conflicts

…-root-alpha-dividends-on-network-removal

# Conflicts:
#	precompiles/src/staking.rs

/// Remove all AllowancesStorage entries whose key contains the given netuid.
pub fn purge_netuid_allowances(netuid: u16) {
let to_remove: Vec<(H160, (H160, u16, u64))> = AllowancesStorage::iter()
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.

The purpose for us to introduce the counter is to keep the allowance even the subnet is de-registered. I will involve the allowance implementer to review this part.

Comment thread pallets/subtensor/src/coinbase/root.rs
@plind-junior plind-junior force-pushed the fix/cleanup-root-alpha-dividends-on-network-removal branch from 645638f to 6c58eba Compare April 27, 2026 15:30
…t_dividends

finalize_all_subnet_root_dividends (called early in remove_network)
already strips this netuid from every RootClaimable map and clears the
RootClaimed prefix. Section 18c was re-stripping the netuid (no-op) and
keeping a dead else-if branch, and section 19 was re-clearing
RootClaimed. Simplify 18c to just garbage-collect entries whose
BTreeMap is now empty, and drop the redundant RootClaimed clear_prefix.
@plind-junior plind-junior force-pushed the fix/cleanup-root-alpha-dividends-on-network-removal branch from 6c58eba to 734419e Compare April 27, 2026 15:31
…-root-alpha-dividends-on-network-removal

# Conflicts:
#	pallets/subtensor/src/macros/config.rs
Comment thread pallets/subtensor/src/coinbase/root.rs Outdated
// 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> =
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.

hot from the second key of storage Keys. it can not be duplicated. use Vec is ok.

@open-junius
Copy link
Copy Markdown
Contributor

open-junius commented Apr 29, 2026

In general, the PR looks good to me. For these gc code, I am not sure if we really need it. It can identify some unneeded data like LastColdkeyHotkeyStakeBlock, but some operation need to scan a lot of data. It is risky to consume too much weights.

subnet_hotkeys is built from Keys::iter_prefix(netuid) where uid is the
unique second key, so it cannot contain duplicates. BTreeSet was
unnecessary — Vec is sufficient and slightly cheaper for this small
bounded list.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-cargo-audit This PR fails cargo audit but needs to be merged anyway skip-validate-benchmarks This PR fails validate benchmarks but needs to be merged anyway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants