Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions pallets/subtensor/src/coinbase/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,28 @@ impl<T: Config> Pallet<T> {
StakingOperationRateLimiter::<T>::remove((hot, cold, netuid));
}
}
// AutoStakeDestination: (cold, netuid) → hot. Without this cleanup, a
// stale destination from a dissolved subnet would silently redirect
// mining incentive when the same netuid is later re-registered (see
// `run_coinbase` auto-stake path).
{
let to_rm: sp_std::vec::Vec<T::AccountId> = AutoStakeDestination::<T>::iter()
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There are 14861 keys on mainnet and 12066 for AutoStakeDestinationColdkeys, so we should be good.

.filter_map(|(cold, n, _)| if n == netuid { Some(cold) } else { None })
.collect();
for cold in to_rm {
AutoStakeDestination::<T>::remove(&cold, netuid);
}
}
// AutoStakeDestinationColdkeys: (hot, netuid) → Vec<cold>. Companion
// reverse-index to AutoStakeDestination; must be cleared in lockstep.
{
let to_rm: sp_std::vec::Vec<T::AccountId> = AutoStakeDestinationColdkeys::<T>::iter()
.filter_map(|(hot, n, _)| if n == netuid { Some(hot) } else { None })
.collect();
for hot in to_rm {
AutoStakeDestinationColdkeys::<T>::remove(&hot, netuid);
}
}

// --- 22. Subnet leasing: remove mapping and any lease-scoped state linked to this netuid.
if let Some(lease_id) = SubnetUidToLeaseId::<T>::take(netuid) {
Expand Down
43 changes: 43 additions & 0 deletions pallets/subtensor/src/tests/networks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ fn dissolve_clears_all_per_subnet_storages() {
// EVM association indexed by (netuid, uid)
AssociatedEvmAddress::<Test>::insert(net, 0u16, (sp_core::H160::zero(), 1u64));

// Auto-stake destination (cold,netuid) -> hot + reverse index
AutoStakeDestination::<Test>::insert(owner_cold, net, owner_hot);
AutoStakeDestinationColdkeys::<Test>::mutate(owner_hot, net, |v| v.push(owner_cold));

// (Optional) subnet -> lease link
SubnetUidToLeaseId::<Test>::insert(net, 42u32);

Expand Down Expand Up @@ -626,13 +630,52 @@ fn dissolve_clears_all_per_subnet_storages() {
// Subnet -> lease link
assert!(!SubnetUidToLeaseId::<Test>::contains_key(net));

// Auto-stake destination + reverse index cleared
assert!(AutoStakeDestination::<Test>::get(owner_cold, net).is_none());
assert!(AutoStakeDestinationColdkeys::<Test>::get(owner_hot, net).is_empty());

// ------------------------------------------------------------------
// Final subnet removal confirmation
// ------------------------------------------------------------------
assert!(!SubtensorModule::if_subnet_exist(net));
});
}

// Focused regression for the AutoStakeDestination orphan: without cleanup on
// dissolve, a stale (coldkey, netuid) → hotkey mapping would survive the
// subnet's dissolution and silently redirect mining incentive when the same
// netuid is later re-registered (see `coinbase::run_coinbase` auto-stake
// path). This test proves the cleanup wipes both halves of the index.
#[test]
fn dissolve_clears_auto_stake_destination_preventing_stale_routing() {
new_test_ext(0).execute_with(|| {
let owner_cold = U256::from(101);
let owner_hot = U256::from(102);
let net = add_dynamic_network(&owner_hot, &owner_cold);

let staker_cold = U256::from(201);
let stale_dest_hot = U256::from(202);

AutoStakeDestination::<Test>::insert(staker_cold, net, stale_dest_hot);
AutoStakeDestinationColdkeys::<Test>::mutate(stale_dest_hot, net, |v| v.push(staker_cold));

// Sanity: both halves of the index are populated before dissolve.
assert_eq!(
AutoStakeDestination::<Test>::get(staker_cold, net),
Some(stale_dest_hot)
);
assert_eq!(
AutoStakeDestinationColdkeys::<Test>::get(stale_dest_hot, net),
vec![staker_cold]
);

assert_ok!(SubtensorModule::do_dissolve_network(net));

assert!(AutoStakeDestination::<Test>::get(staker_cold, net).is_none());
assert!(AutoStakeDestinationColdkeys::<Test>::get(stale_dest_hot, net).is_empty());
});
}

#[test]
fn dissolve_alpha_out_but_zero_tao_no_rewards() {
new_test_ext(0).execute_with(|| {
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,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: 406,
spec_version: 407,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
Loading