Skip to content

Fix swap_hotkey_v2: moving locks and conviction#2729

Open
gztensor wants to merge 4 commits into
devnet-readyfrom
fix/hotkey-swap-conviction
Open

Fix swap_hotkey_v2: moving locks and conviction#2729
gztensor wants to merge 4 commits into
devnet-readyfrom
fix/hotkey-swap-conviction

Conversation

@gztensor
Copy link
Copy Markdown
Contributor

@gztensor gztensor commented Jun 8, 2026

Description

Hotkey swap v2 did not move conviction. This PR fixes the issue.

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):

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

@gztensor gztensor added the skip-cargo-audit This PR fails cargo audit but needs to be merged anyway label Jun 8, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment thread pallets/subtensor/src/swap/swap_hotkey.rs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

🛡️ AI Review — Skeptic (security review)

VERDICT: VULNERABLE

Baseline scrutiny: author has write permission and substantial prior subtensor history; branch fix/hotkey-swap-conviction -> devnet-ready; no .github, dependency, build-script, or lockfile changes.

Static review only, per Skeptic constraints. The previous global-lock-migration finding is addressed by the new subnet-scoped lock swap path, but the added lock movement still introduces unbounded storage scans in runtime paths.

Findings

Sev File Finding
HIGH pallets/subtensor/src/migrations/migrate_fix_subnet_hotkey_lock_swaps.rs:299 Runtime upgrade migration undercounts a full Lock iteration inline
HIGH pallets/subtensor/src/swap/swap_hotkey.rs:354 Subnet hotkey swap scans the entire lock map under fixed weight inline

Prior-comment reconciliation

  • 390b025e: addressed — The subnet-specific swap path now calls swap_hotkey_locks_on_subnet(old_hotkey, new_hotkey, netuid) rather than the global swap_hotkey_locks, and the helper filters transfers to the requested netuid.

Conclusion

The PR is vulnerable because the new subnet hotkey-swap lock migration can scan the entire lock map under a fixed normal extrinsic weight, and the runtime upgrade migration also scans the full map while under-reporting that work. Both paths need bounded/indexed iteration or conservative weight handling before merge.


📜 Previous run (superseded)
Sev File Finding Status
HIGH pallets/subtensor/src/swap/swap_hotkey.rs:354 Subnet swaps migrate locks for every subnet ✅ Addressed
The subnet-specific swap path now calls swap_hotkey_locks_on_subnet(old_hotkey, new_hotkey, netuid) rather than the global swap_hotkey_locks, and the helper filters transfers to the requested netuid.

# 🔍 AI Review — Auditor (domain review) has not yet run on this PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

🔄 AI review updated — Skeptic: VULNERABLE

@gztensor gztensor self-assigned this Jun 8, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.

Comment thread pallets/subtensor/src/swap/swap_hotkey.rs Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

🔄 AI review updated — Skeptic: VULNERABLE

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

AI review — see the sticky summary comment for the verdict and the inline comments below for specific findings.


// 9. Perform the hotkey swap
// 9. Swap the stake locks
let (reads, writes) = Self::swap_hotkey_locks_on_subnet(old_hotkey, new_hotkey, netuid);
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.

[HIGH] Subnet hotkey swap scans the entire lock map under fixed weight

This added call reaches swap_hotkey_locks_for_netuids, which uses Lock::<T>::iter() whenever the old hotkey has a relevant aggregate lock on the subnet. swap_hotkey_v2 is still declared with a fixed normal-class pre-dispatch weight, so a single subnet-scoped swap can force a full scan of all (coldkey, netuid, hotkey) lock entries while only being admitted to the block at the static weight. Since Lock is keyed by coldkey first, this cannot be bounded by the target subnet/hotkey without another index or a capped migration-style path. This is a transaction-level DoS/overweight-block risk. Use a bounded/reverse index for locks to old_hotkey on netuid, or do not expose this full-map scan from the normal extrinsic path.

Comment on lines +285 to +299
let locks: Vec<(T::AccountId, LockState)> = Lock::<T>::iter()
.filter_map(|((coldkey, lock_netuid, hotkey), lock)| {
(lock_netuid == netuid && hotkey == old_hotkey).then_some((coldkey, lock))
})
.collect();
for (coldkey, _) in &locks {
Lock::<T>::remove((coldkey.clone(), netuid, old_hotkey.clone()));
}
locks
};
let locks_to_fix_count = locks_to_fix.len() as u64;
weight = weight.saturating_add(
T::DbWeight::get()
.reads_writes(locks_to_fix_count.saturating_add(1), locks_to_fix_count),
);
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.

[HIGH] Runtime upgrade migration undercounts a full Lock iteration

For the coldkey: None fix, this branch iterates every Lock entry and only filters after reading each item. The returned migration weight then charges locks_to_fix_count + 1 reads, which counts only matching locks, not the full map scan. On a large lock map, the runtime upgrade can do substantially more work than it reports, creating an overweight runtime-upgrade block risk. Either avoid the global iterator by enumerating exact coldkeys, or account for every scanned entry and keep the migration within a bounded, known-safe limit.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 8, 2026

🔄 AI review updated — Skeptic: VULNERABLE

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant