code/pallets/subtensor/src/swap/swap_hotkey.rs
use super::*;
use frame_support::weights::Weight;
use share_pool::SafeFloat;
use sp_core::Get;
use sp_std::collections::btree_map::BTreeMap;
use subtensor_runtime_common::{MechId, NetUid, Token};
struct PreparedHotkeyStake<AccountId> {
positions: Vec<(AccountId, NetUid, SafeFloat)>,
coldkeys_by_netuid: BTreeMap<NetUid, Vec<AccountId>>,
}
impl<T: Config> Pallet<T> {
/// Use the generated all-subnet stake-moving benchmark for every v2 path
/// that scans and moves stake. Preserve the previous lightweight weight for
/// the single-subnet `keep_stake` path, which does not scan stake prefixes.
pub fn swap_hotkey_v2_dispatch_weight(netuid: &Option<NetUid>, keep_stake: bool) -> Weight {
if netuid.is_none() || !keep_stake {
<<T as crate::pallet::Config>::WeightInfo as crate::weights::WeightInfo>::swap_hotkey()
} else {
Weight::from_parts(275_300_000, 0)
.saturating_add(T::DbWeight::get().reads(52_u64))
.saturating_add(T::DbWeight::get().writes(35_u64))
}
}
/// Read and merge the old hotkey's V1/V2 stake rows once. V2 keeps the
/// existing precedence over a duplicate legacy row.
fn prepare_hotkey_stake(old_hotkey: &T::AccountId) -> PreparedHotkeyStake<T::AccountId> {
let positions: Vec<(T::AccountId, NetUid, SafeFloat)> =
Self::alpha_iter_single_prefix(old_hotkey).collect();
let mut coldkeys_by_netuid: BTreeMap<NetUid, Vec<T::AccountId>> = BTreeMap::new();
for (coldkey, netuid, _) in &positions {
coldkeys_by_netuid
.entry(*netuid)
.or_default()
.push(coldkey.clone());
}
PreparedHotkeyStake {
positions,
coldkeys_by_netuid,
}
}
/// Swaps the hotkey of a coldkey account.
///
/// # Arguments
///
/// * `origin`: The origin of the transaction, and also the coldkey account.
/// * `old_hotkey`: The old hotkey to be swapped.
/// * `new_hotkey`: The new hotkey to replace the old one.
/// * `netuid`: The hotkey swap in a subnet or all subnets.
/// * `keep_stake`: If `true`, stake remains on the old hotkey and the rest metadata
///
/// # Returns
///
/// * `DispatchResultWithPostInfo`: The result of the dispatch.
///
/// # Errors
///
/// * `NonAssociatedColdKey`: If the coldkey does not own the old hotkey.
/// * `NewHotKeyIsSameWithOld`: If the new hotkey is the same as the old hotkey.
/// * `HotKeyAlreadyRegisteredInSubNet`: If the new hotkey is already registered in the subnet.
/// * `NewHotKeyNotCleanForRootSwap`: If the swap touches root and the new hotkey
/// has outstanding `RootClaimable` entries or non-zero root stake.
/// * `NotEnoughBalanceToPaySwapHotKey`: If there is not enough balance to pay for the swap.
pub fn do_swap_hotkey(
origin: OriginFor<T>,
old_hotkey: &T::AccountId,
new_hotkey: &T::AccountId,
netuid: Option<NetUid>,
keep_stake: bool,
) -> DispatchResultWithPostInfo {
// // 1. Ensure the origin is signed and get the coldkey
let coldkey = ensure_signed(origin)?;
if let Some(netuid) = netuid {
ensure!(Self::if_subnet_exist(netuid), Error::<T>::SubnetNotExists);
}
// 2. Ensure the coldkey owns the old hotkey
ensure!(
Self::coldkey_owns_hotkey(&coldkey, old_hotkey),
Error::<T>::NonAssociatedColdKey
);
// 3. Initialize the weight for this operation. The coldkey/old_hotkey
// ownership check above reads Owner twice.
let mut weight = T::DbWeight::get().reads(2);
// 4. If the new hotkey already exists globally, ensure the coldkey owns it
weight.saturating_accrue(T::DbWeight::get().reads(1));
if Self::hotkey_account_exists(new_hotkey) {
weight.saturating_accrue(T::DbWeight::get().reads(2));
ensure!(
Self::coldkey_owns_hotkey(&coldkey, new_hotkey),
Error::<T>::NonAssociatedColdKey
);
} else {
weight.saturating_accrue(T::DbWeight::get().reads(1));
}
// 5. Ensure the new hotkey is different from the old one
ensure!(old_hotkey != new_hotkey, Error::<T>::NewHotKeyIsSameWithOld);
// 6. Get the current block number
let block: u64 = Self::get_current_block_as_u64();
match netuid {
// 8. Ensure the hotkey is not registered on the network before, if netuid is provided
Some(netuid) => {
ensure!(
!Self::is_hotkey_registered_on_specific_network(new_hotkey, netuid),
Error::<T>::HotKeyAlreadyRegisteredInSubNet
);
}
// 8.1 Ensure the new hotkey is not already registered on any network, only if netuid is none
None => {
ensure!(
!Self::is_hotkey_registered_on_any_network(new_hotkey),
Error::<T>::HotKeyAlreadyRegisteredInSubNet
);
}
}
// 8.2 If the swap touches the root subnet, require that new_hotkey is clean
// on root (no outstanding claimable rate and no existing root stake). Merging
// a non-empty rate-book would either violate total conservation or misallocate
// dividends across coldkeys that never staked on old_hotkey.
let touches_root = match netuid {
None => true,
Some(n) => n == NetUid::ROOT,
};
if touches_root {
ensure!(
RootClaimable::<T>::get(new_hotkey).is_empty()
&& Self::get_stake_for_hotkey_on_subnet(new_hotkey, NetUid::ROOT).is_zero()
&& RootClaimed::<T>::iter_prefix((NetUid::ROOT, new_hotkey))
.next()
.is_none(),
Error::<T>::NewHotKeyNotCleanForRootSwap
);
}
// Read and group stake once before any hotkey-swap mutation. Execution
// reuses this snapshot instead of rescanning both prefixes per subnet.
let prepared_stake = if keep_stake {
None
} else {
Some(Self::prepare_hotkey_stake(old_hotkey))
};
// 8. Swap LastTxBlockDelegateTake
let last_tx_block_delegate_take: u64 = Self::get_last_tx_block_delegate_take(old_hotkey);
Self::set_last_tx_block_delegate_take(new_hotkey, last_tx_block_delegate_take);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
// 11. Swap LastTxBlockChildKeyTake
let last_tx_block_child_key_take: u64 = Self::get_last_tx_block_childkey_take(old_hotkey);
Self::set_last_tx_block_childkey(new_hotkey, last_tx_block_child_key_take);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
// 12. fork for swap hotkey on a specific subnet case after do the common check
if let Some(netuid) = netuid {
return Self::swap_hotkey_on_subnet(
&coldkey,
old_hotkey,
new_hotkey,
netuid,
weight,
keep_stake,
prepared_stake.as_ref(),
);
};
// Start to do everything for swap hotkey on all subnets case
// 12.1 Enforce the per-subnet hotkey-swap cooldown on the all-subnets path too.
// The all-subnets swap moves the identity on every subnet the old hotkey actively
// participates in, so it must respect (and record) `LastHotkeySwapOnNetuid` for each
// of those subnets, exactly like the per-subnet path.
//
// "Participates in" is membership OR being a parent (has childkeys). Note a parent
// need not be a registered member — `do_set_children` has no membership requirement —
// so the childkey case genuinely adds coverage beyond `IsNetworkMember`, and
// `parent_child_swap_hotkey` re-homes those childkeys even on non-member subnets.
//
// We deliberately do NOT gate on the *child* side (`ParentKeys`): being someone's
// child is set by the parent via `do_set_children` WITHOUT the child's consent, so a
// third party could otherwise add a victim's hotkey as a child on an arbitrary subnet
// and impose swap-cooldowns on it — a griefing vector. The swap still migrates the
// child relationship for correctness; it simply isn't cooldown-gated.
let hotkey_swap_interval = T::HotkeySwapOnSubnetInterval::get();
let all_netuids = Self::get_all_subnet_netuids();
// Up to 2 reads per subnet during filtering (membership + childkeys), plus the
// subnet-list read itself.
weight.saturating_accrue(
T::DbWeight::get().reads(
(all_netuids.len() as u64)
.saturating_mul(2)
.saturating_add(1),
),
);
let affected_netuids: Vec<NetUid> = all_netuids
.into_iter()
.filter(|netuid| {
IsNetworkMember::<T>::get(old_hotkey, *netuid)
|| !ChildKeys::<T>::get(old_hotkey, *netuid).is_empty()
})
.collect();
for netuid in affected_netuids.iter() {
let last_hotkey_swap_block = LastHotkeySwapOnNetuid::<T>::get(*netuid, &coldkey);
// Only enforce the cooldown when a prior swap was recorded on this subnet.
// A first swap (no recorded timestamp) must not be gated by chain age — that
// would block the very first swap and never closes a bypass, since any swap
// records the timestamp and subsequent swaps within the interval are rejected.
if last_hotkey_swap_block != 0 {
ensure!(
last_hotkey_swap_block.saturating_add(hotkey_swap_interval) < block,
Error::<T>::HotKeySwapOnSubnetIntervalNotPassed
);
}
weight.saturating_accrue(T::DbWeight::get().reads(1));
}
// Start to do everything for swap hotkey on all subnets case
// 13. Get the cost for swapping the key
let swap_cost = Self::get_key_swap_cost();
log::debug!("Swap cost: {swap_cost:?}");
// 14. Ensure the coldkey has enough balance to pay for the swap
ensure!(
Self::can_remove_balance_from_coldkey_account(&coldkey, swap_cost.into()),
Error::<T>::NotEnoughBalanceToPaySwapHotKey
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 0));
// 15. Remove the swap cost from the coldkey's account + Recycle the tokens
Self::recycle_tao(&coldkey, swap_cost.into())?;
weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 2));
// 16. Perform the hotkey swap using the read-only snapshot prepared
// before the fee was charged.
Self::perform_hotkey_swap_on_all_subnets_prepared(
old_hotkey,
new_hotkey,
&coldkey,
&mut weight,
keep_stake,
prepared_stake.as_ref(),
)?;
// 16.1 Record the per-subnet swap cooldown for every affected subnet, so a
// subsequent per-subnet (or all-subnets) swap on the same subnet within the
// interval is correctly rejected.
for netuid in affected_netuids.iter() {
LastHotkeySwapOnNetuid::<T>::insert(*netuid, &coldkey, block);
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
// 17. Emit an event for the hotkey swap
Self::deposit_event(Event::HotkeySwapped {
coldkey,
old_hotkey: old_hotkey.clone(),
new_hotkey: new_hotkey.clone(),
});
// Stake-moving paths retain the generated pre-dispatch benchmark weight.
// `keep_stake` does not inspect stake prefixes and may keep its dynamic refund.
if keep_stake {
Ok(Some(weight).into())
} else {
Ok(None.into())
}
}
/// Performs the hotkey swap operation, transferring all associated data and state from the old hotkey to the new hotkey.
///
/// This function executes a series of steps to ensure a complete transfer of all relevant information:
/// 1. Swaps the owner of the hotkey.
/// 2. Updates the list of owned hotkeys for the coldkey.
/// 3. Transfers the total hotkey stake.
/// 4. Moves all stake-related data for the interval.
/// 5. Updates the last transaction block for the new hotkey.
/// 6. Transfers the delegate take information.
/// 7. Updates delegate information.
/// 8. For each subnet:
/// - Updates network membership status.
/// - Transfers UID and key information.
/// - Moves Prometheus data.
/// - Updates axon information.
/// - Transfers weight commits.
/// - Updates loaded emission data.
/// 9. Transfers all stake information, including updating staking hotkeys for each coldkey.
///
/// Throughout the process, the function accumulates the computational weight of operations performed.
///
/// # Arguments
/// * `old_hotkey`: The AccountId of the current hotkey to be replaced.
/// * `new_hotkey`: The AccountId of the new hotkey to replace the old one.
/// * `coldkey`: The AccountId of the coldkey that owns both hotkeys.
/// * `weight`: A mutable reference to the Weight, updated as operations are performed.
///
/// # Returns
/// * `DispatchResult`: Ok(()) if the swap was successful, or an error if any operation failed.
///
/// # Note
/// This function performs extensive storage reads and writes, which can be computationally expensive.
/// The accumulated weight should be carefully considered in the context of block limits.
pub fn perform_hotkey_swap_on_all_subnets(
old_hotkey: &T::AccountId,
new_hotkey: &T::AccountId,
coldkey: &T::AccountId,
weight: &mut Weight,
keep_stake: bool,
) -> DispatchResult {
let prepared_stake = if keep_stake {
None
} else {
Some(Self::prepare_hotkey_stake(old_hotkey))
};
Self::perform_hotkey_swap_on_all_subnets_prepared(
old_hotkey,
new_hotkey,
coldkey,
weight,
keep_stake,
prepared_stake.as_ref(),
)
}
fn perform_hotkey_swap_on_all_subnets_prepared(
old_hotkey: &T::AccountId,
new_hotkey: &T::AccountId,
coldkey: &T::AccountId,
weight: &mut Weight,
keep_stake: bool,
prepared_stake: Option<&PreparedHotkeyStake<T::AccountId>>,
) -> DispatchResult {
// 2. Swap the stake locks
let (reads, writes) = Self::swap_hotkey_locks(old_hotkey, new_hotkey);
weight.saturating_accrue(T::DbWeight::get().reads_writes(reads, writes));
// 3. Swap owner.
// Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey.
Owner::<T>::remove(old_hotkey);
Self::set_hotkey_owner(coldkey, new_hotkey)?;
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
// 4. Swap OwnedHotkeys.
// OwnedHotkeys( coldkey ) -> Vec<hotkey> -- the hotkeys that the coldkey owns.
let mut hotkeys = OwnedHotkeys::<T>::get(coldkey);
// Add the new key if needed.
if !hotkeys.contains(new_hotkey) {
hotkeys.push(new_hotkey.clone());
}
// 5. Remove the old key.
hotkeys.retain(|hk| *hk != *old_hotkey);
OwnedHotkeys::<T>::insert(coldkey, hotkeys);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
// 6. execute the hotkey swap on all subnets
for netuid in Self::get_all_subnet_netuids() {
let stake_coldkeys = prepared_stake
.and_then(|prepared| prepared.coldkeys_by_netuid.get(&netuid))
.map(Vec::as_slice)
.unwrap_or(&[]);
Self::perform_hotkey_swap_on_one_subnet_prepared(
old_hotkey,
new_hotkey,
weight,
netuid,
keep_stake,
stake_coldkeys,
)?;
}
// 7. Swap LastTxBlock
// LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey.
Self::remove_last_tx_block(old_hotkey);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
// 8. Swap LastTxBlockDelegateTake
// LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take.
Self::remove_last_tx_block_delegate_take(old_hotkey);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
// 9. Swap LastTxBlockChildKeyTake
// LastTxBlockChildKeyTake( hotkey ) --> u64 -- the last transaction block for the hotkey child key take.
Self::remove_last_tx_block_childkey(old_hotkey);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
// 10. Swap delegates.
// Delegates( hotkey ) -> take value -- the hotkey delegate take value.
if Delegates::<T>::contains_key(old_hotkey) {
let old_delegate_take = Delegates::<T>::get(old_hotkey);
Delegates::<T>::remove(old_hotkey);
Delegates::<T>::insert(new_hotkey, old_delegate_take);
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
}
// 11. Alphas already update in perform_hotkey_swap_on_one_subnet
// Update the StakingHotkeys for the case where hotkey staked by multiple coldkeys.
if let Some(prepared_stake) = prepared_stake {
for (coldkey, _netuid, alpha_share) in &prepared_stake.positions {
// Swap StakingHotkeys.
// StakingHotkeys( coldkey ) --> Vec<hotkey> -- the hotkeys that the coldkey stakes.
if !alpha_share.is_zero() {
let mut staking_hotkeys = StakingHotkeys::<T>::get(coldkey);
weight.saturating_accrue(T::DbWeight::get().reads(1));
if staking_hotkeys.contains(old_hotkey) {
staking_hotkeys.retain(|hk| *hk != *old_hotkey && *hk != *new_hotkey);
if !staking_hotkeys.contains(new_hotkey) {
staking_hotkeys.push(new_hotkey.clone());
}
StakingHotkeys::<T>::insert(coldkey, staking_hotkeys);
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
}
}
}
// Return successful after swapping all the relevant terms.
Ok(())
}
#[allow(unused)]
fn swap_hotkey_on_subnet(
coldkey: &T::AccountId,
old_hotkey: &T::AccountId,
new_hotkey: &T::AccountId,
netuid: NetUid,
init_weight: Weight,
keep_stake: bool,
prepared_stake: Option<&PreparedHotkeyStake<T::AccountId>>,
) -> DispatchResultWithPostInfo {
// 1. Ensure coldkey not swap hotkey too frequently
let mut weight: Weight = init_weight;
let block: u64 = Self::get_current_block_as_u64();
let hotkey_swap_interval = T::HotkeySwapOnSubnetInterval::get();
let last_hotkey_swap_block = LastHotkeySwapOnNetuid::<T>::get(netuid, coldkey);
ensure!(
last_hotkey_swap_block.saturating_add(hotkey_swap_interval) < block,
Error::<T>::HotKeySwapOnSubnetIntervalNotPassed
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(3, 0));
// Check that new hotkey is a non-system hotkey
ensure!(
Self::is_subnet_account_id(new_hotkey).is_none(),
Error::<T>::CannotUseSystemAccount
);
// 2. Ensure the hotkey not registered on the network before.
ensure!(
!Self::is_hotkey_registered_on_specific_network(new_hotkey, netuid),
Error::<T>::HotKeyAlreadyRegisteredInSubNet
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0));
// 3. Get the cost for swapping the key on the subnet
let swap_cost = T::KeySwapOnSubnetCost::get();
log::debug!("Swap cost in subnet {netuid:?}: {swap_cost:?}");
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 0));
// 4. Ensure the coldkey has enough balance to pay for the swap
ensure!(
Self::can_remove_balance_from_coldkey_account(coldkey, swap_cost),
Error::<T>::NotEnoughBalanceToPaySwapHotKey
);
// 5. Remove the swap cost from the coldkey's account + Recycle the tokens
Self::recycle_tao(coldkey, swap_cost)?;
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
// 7. Swap owner.
// Owner( hotkey ) -> coldkey -- the coldkey that owns the hotkey.
// Owner::<T>::remove(old_hotkey);
Owner::<T>::insert(new_hotkey, coldkey.clone());
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
// 8. Swap OwnedHotkeys.
// OwnedHotkeys( coldkey ) -> Vec<hotkey> -- the hotkeys that the coldkey owns.
let mut hotkeys = OwnedHotkeys::<T>::get(coldkey);
// Add the new key if needed.
if !hotkeys.contains(new_hotkey) {
hotkeys.push(new_hotkey.clone());
OwnedHotkeys::<T>::insert(coldkey, hotkeys);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1));
}
// 9. Swap the stake locks
let (reads, writes) = Self::swap_hotkey_locks_on_subnet(old_hotkey, new_hotkey, netuid);
weight.saturating_accrue(T::DbWeight::get().reads_writes(reads, writes));
// 10. Perform the hotkey swap using the preflight snapshot.
let stake_coldkeys = prepared_stake
.and_then(|prepared| prepared.coldkeys_by_netuid.get(&netuid))
.map(Vec::as_slice)
.unwrap_or(&[]);
Self::perform_hotkey_swap_on_one_subnet_prepared(
old_hotkey,
new_hotkey,
&mut weight,
netuid,
keep_stake,
stake_coldkeys,
)?;
// 10. Record the per-subnet swap block for the HotkeySwapOnSubnetInterval gate.
// The generic LastTxBlock setter is dropped together with its removed check.
LastHotkeySwapOnNetuid::<T>::insert(netuid, coldkey, block);
weight.saturating_accrue(T::DbWeight::get().writes(1));
// 12. Emit an event for the hotkey swap
Self::deposit_event(Event::HotkeySwappedOnSubnet {
coldkey: coldkey.clone(),
old_hotkey: old_hotkey.clone(),
new_hotkey: new_hotkey.clone(),
netuid,
});
if keep_stake {
Ok(Some(weight).into())
} else {
Ok(None.into())
}
}
// do hotkey swap public part for both swap all subnets and just swap one subnet
pub fn perform_hotkey_swap_on_one_subnet(
old_hotkey: &T::AccountId,
new_hotkey: &T::AccountId,
weight: &mut Weight,
netuid: NetUid,
keep_stake: bool,
) -> DispatchResult {
let prepared_stake = if keep_stake {
None
} else {
Some(Self::prepare_hotkey_stake(old_hotkey))
};
let stake_coldkeys = prepared_stake
.as_ref()
.and_then(|prepared| prepared.coldkeys_by_netuid.get(&netuid))
.map(Vec::as_slice)
.unwrap_or(&[]);
Self::perform_hotkey_swap_on_one_subnet_prepared(
old_hotkey,
new_hotkey,
weight,
netuid,
keep_stake,
stake_coldkeys,
)
}
fn perform_hotkey_swap_on_one_subnet_prepared(
old_hotkey: &T::AccountId,
new_hotkey: &T::AccountId,
weight: &mut Weight,
netuid: NetUid,
keep_stake: bool,
stake_coldkeys: &[T::AccountId],
) -> DispatchResult {
// 3. Swap all subnet specific info.
// 3.1 Remove the previous hotkey and insert the new hotkey from membership.
// IsNetworkMember( hotkey, netuid ) -> bool -- is the hotkey a subnet member.
let is_network_member: bool = IsNetworkMember::<T>::get(old_hotkey, netuid);
IsNetworkMember::<T>::remove(old_hotkey, netuid);
IsNetworkMember::<T>::insert(new_hotkey, netuid, is_network_member);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
// 3.2 Swap Uids + Keys.
// Keys( netuid, hotkey ) -> uid -- the uid the hotkey has in the network if it is a member.
// Uids( netuid, hotkey ) -> uid -- the uids that the hotkey has.
if is_network_member {
// 3.2.1 Swap the UIDS
if let Ok(old_uid) = Uids::<T>::try_get(netuid, old_hotkey) {
Uids::<T>::remove(netuid, old_hotkey);
Uids::<T>::insert(netuid, new_hotkey, old_uid);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
// 3.2.2 Swap the keys.
Keys::<T>::insert(netuid, old_uid, new_hotkey.clone());
weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
}
}
// 3.3 Swap Prometheus.
// Prometheus( netuid, hotkey ) -> prometheus -- the prometheus data that a hotkey has in the network.
if is_network_member
&& let Ok(old_prometheus_info) = Prometheus::<T>::try_get(netuid, old_hotkey)
{
Prometheus::<T>::remove(netuid, old_hotkey);
Prometheus::<T>::insert(netuid, new_hotkey, old_prometheus_info);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
// 3.4. Swap axons.
// Axons( netuid, hotkey ) -> axon -- the axon that the hotkey has.
if is_network_member && let Ok(old_axon_info) = Axons::<T>::try_get(netuid, old_hotkey) {
Axons::<T>::remove(netuid, old_hotkey);
Axons::<T>::insert(netuid, new_hotkey, old_axon_info);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
// 3.5 Swap WeightCommits
// WeightCommits( hotkey ) --> Vec<u64> -- the weight commits for the hotkey.
if is_network_member {
for mecid in 0..MechanismCountCurrent::<T>::get(netuid).into() {
let netuid_index = Self::get_mechanism_storage_index(netuid, MechId::from(mecid));
if let Ok(old_weight_commits) =
WeightCommits::<T>::try_get(netuid_index, old_hotkey)
{
WeightCommits::<T>::remove(netuid_index, old_hotkey);
WeightCommits::<T>::insert(netuid_index, new_hotkey, old_weight_commits);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
}
}
// 3.6. Swap the subnet loaded emission.
// LoadedEmission( netuid ) --> Vec<(hotkey, u64)> -- the loaded emission for the subnet.
if is_network_member && let Some(mut old_loaded_emission) = LoadedEmission::<T>::get(netuid)
{
for emission in old_loaded_emission.iter_mut() {
if emission.0 == *old_hotkey {
emission.0 = new_hotkey.clone();
}
}
LoadedEmission::<T>::remove(netuid);
LoadedEmission::<T>::insert(netuid, old_loaded_emission);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
// 3.7. Swap neuron TLS certificates.
// NeuronCertificates( netuid, hotkey ) -> Vec<u8> -- the neuron certificate for the hotkey.
if is_network_member
&& let Ok(old_neuron_certificates) =
NeuronCertificates::<T>::try_get(netuid, old_hotkey)
{
NeuronCertificates::<T>::remove(netuid, old_hotkey);
NeuronCertificates::<T>::insert(netuid, new_hotkey, old_neuron_certificates);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
// 3.8. Swap ChildkeyTake.
// ChildkeyTake( hotkey, netuid ) --> u16 -- the per-subnet childkey take for the hotkey.
// Only migrate when an explicit value exists, to preserve the storage default
// semantics (don't materialize a floor value for hotkeys that never set a take).
// `take` reads + removes the old row in one operation, avoiding orphaned storage.
if ChildkeyTake::<T>::contains_key(old_hotkey, netuid) {
let childkey_take = ChildkeyTake::<T>::take(old_hotkey, netuid);
ChildkeyTake::<T>::insert(new_hotkey, netuid, childkey_take);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
// 4. Swap ChildKeys.
// 5. Swap ParentKeys.
// 6. Swap PendingChildKeys.
Self::parent_child_swap_hotkey(old_hotkey, new_hotkey, netuid, weight)?;
// Also check for others with our hotkey as a child
for (hotkey, (children, cool_down_block)) in PendingChildKeys::<T>::iter_prefix(netuid) {
weight.saturating_accrue(T::DbWeight::get().reads(1));
if let Some(potential_idx) =
children.iter().position(|(_, child)| *child == *old_hotkey)
{
let mut new_children = children.clone();
let entry_to_remove = new_children.remove(potential_idx);
new_children.push((entry_to_remove.0, new_hotkey.clone())); // Keep the proportion.
PendingChildKeys::<T>::remove(netuid, hotkey.clone());
PendingChildKeys::<T>::insert(netuid, hotkey, (new_children, cool_down_block));
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
}
// 6.4 Swap AutoStakeDestination
if let Ok(old_auto_stake_coldkeys) =
AutoStakeDestinationColdkeys::<T>::try_get(old_hotkey, netuid)
{
// Move the vector from old hotkey to new hotkey.
for coldkey in &old_auto_stake_coldkeys {
AutoStakeDestination::<T>::insert(coldkey, netuid, new_hotkey);
}
AutoStakeDestinationColdkeys::<T>::remove(old_hotkey, netuid);
AutoStakeDestinationColdkeys::<T>::insert(new_hotkey, netuid, old_auto_stake_coldkeys);
}
// 7. Swap SubnetOwnerHotkey
// SubnetOwnerHotkey( netuid ) --> hotkey -- the hotkey that is the owner of the subnet.
if let Ok(old_subnet_owner_hotkey) = SubnetOwnerHotkey::<T>::try_get(netuid) {
weight.saturating_accrue(T::DbWeight::get().reads(1));
if old_subnet_owner_hotkey == *old_hotkey {
Self::set_subnet_owner_hotkey(netuid, new_hotkey)?;
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
}
// 8. Swap dividend records
if !keep_stake {
// 8.1 Swap TotalHotkeyAlphaLastEpoch
let old_alpha = TotalHotkeyAlphaLastEpoch::<T>::take(old_hotkey, netuid);
let new_total_hotkey_alpha = TotalHotkeyAlphaLastEpoch::<T>::get(new_hotkey, netuid);
TotalHotkeyAlphaLastEpoch::<T>::insert(
new_hotkey,
netuid,
old_alpha.saturating_add(new_total_hotkey_alpha),
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
// 8.2 Swap AlphaDividendsPerSubnet
let old_hotkey_alpha_dividends = AlphaDividendsPerSubnet::<T>::get(netuid, old_hotkey);
let new_hotkey_alpha_dividends = AlphaDividendsPerSubnet::<T>::get(netuid, new_hotkey);
AlphaDividendsPerSubnet::<T>::remove(netuid, old_hotkey);
AlphaDividendsPerSubnet::<T>::insert(
netuid,
new_hotkey,
old_hotkey_alpha_dividends.saturating_add(new_hotkey_alpha_dividends),
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
// 8.3 Swap TaoDividendsPerSubnet
// Tao dividends were removed
// 8.4 Swap VotingPower
// VotingPower( netuid, hotkey ) --> u64 -- the voting power EMA for the hotkey.
Self::swap_voting_power_for_hotkey(old_hotkey, new_hotkey, netuid);
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
// Move only the positions prepared for this subnet. The V1/V2
// prefixes were already scanned and deduplicated once during preflight.
for coldkey in stake_coldkeys {
let alpha_old =
Self::get_stake_for_hotkey_and_coldkey_on_subnet(old_hotkey, coldkey, netuid);
Self::decrease_stake_for_hotkey_and_coldkey_on_subnet(
old_hotkey, coldkey, netuid, alpha_old,
);
Self::increase_stake_for_hotkey_and_coldkey_on_subnet(
new_hotkey, coldkey, netuid, alpha_old,
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
let mut staking_hotkeys = StakingHotkeys::<T>::get(coldkey);
weight.saturating_accrue(T::DbWeight::get().reads(1));
if staking_hotkeys.contains(old_hotkey) && !staking_hotkeys.contains(new_hotkey) {
staking_hotkeys.push(new_hotkey.clone());
StakingHotkeys::<T>::insert(coldkey, staking_hotkeys);
weight.saturating_accrue(T::DbWeight::get().writes(1));
}
}
if netuid == NetUid::ROOT {
// 9. Transfer root claimable and root claimed only for the root subnet
// NOTE: we shouldn't transfer root claimable and root claimed for other subnets,
// otherwise root stakers won't be able to receive dividends.
Self::transfer_root_claimable_for_new_hotkey(old_hotkey, new_hotkey);
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
// After transfer, new_hotkey has the full RootClaimable map.
// We use it to know which subnets have outstanding claims.
let subnets: Vec<NetUid> = RootClaimable::<T>::get(new_hotkey)
.keys()
.copied()
.collect();
weight.saturating_accrue(T::DbWeight::get().reads(1));
for subnet in subnets {
let claimed_coldkeys: Vec<T::AccountId> =
RootClaimed::<T>::iter_prefix((subnet, old_hotkey))
.map(|(coldkey, _)| coldkey)
.collect();
weight
.saturating_accrue(T::DbWeight::get().reads(claimed_coldkeys.len() as u64));
for coldkey in claimed_coldkeys {
Self::transfer_root_claimed_for_new_keys(
subnet, old_hotkey, new_hotkey, &coldkey, &coldkey,
);
weight.saturating_accrue(T::DbWeight::get().reads_writes(2, 2));
}
}
// Transfer AutoParentDelegationEnabled flag from old_hotkey to new_hotkey.
// Only migrate if it was explicitly set, to preserve the storage default semantics.
if AutoParentDelegationEnabled::<T>::contains_key(old_hotkey) {
let enabled = AutoParentDelegationEnabled::<T>::take(old_hotkey);
AutoParentDelegationEnabled::<T>::insert(new_hotkey, enabled);
weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2));
}
}
}
Ok(())
}
}