Skip to content

fix: match remove-side NetUid::ROOT guard in add_stake_adjust_root_cl…#2611

Open
boskodev790 wants to merge 2 commits intoopentensor:mainfrom
boskodev790:fix/root-claimed-skip-root-netuid-on-add
Open

fix: match remove-side NetUid::ROOT guard in add_stake_adjust_root_cl…#2611
boskodev790 wants to merge 2 commits intoopentensor:mainfrom
boskodev790:fix/root-claimed-skip-root-netuid-on-add

Conversation

@boskodev790
Copy link
Copy Markdown

…aimed

remove_stake_adjust_root_claimed_for_hotkey_and_coldkey explicitly skips NetUid::ROOT when iterating over RootClaimable, but the add counterpart does not. Under normal operation run_coinbase excludes ROOT from the coinbase loop (run_coinbase.rs:31), so RootClaimable should never carry a ROOT entry and the asymmetry is invisible.

If anything ever inserts a ROOT entry though, RootClaimed would tick up on every stake add but never drain on remove, since only the remove side has the guard. Over enough cycles RootClaimed for the ROOT tuple would exceed claimable and the existing saturating_sub in get_root_owed_* would silently clamp owed to 0.

Mirror the guard in the add path and add a regression test that seeds RootClaimable with both a ROOT entry and a normal netuid entry, calls add_stake_adjust_root_claimed, and checks ROOT stays at 0 while the normal entry is bumped by rate * amount.

Description

Related Issue(s)

  • Closes #[issue number]

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

If this PR introduces a breaking change, please provide a detailed description of the impact and the migration path for existing applications.

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

Screenshots (if applicable)

Please include any relevant screenshots or GIFs that demonstrate the changes made.

Additional Notes

Please provide any additional information or context that may be helpful for reviewers.

…aimed

remove_stake_adjust_root_claimed_for_hotkey_and_coldkey explicitly skips
NetUid::ROOT when iterating over RootClaimable, but the add counterpart
does not. Under normal operation run_coinbase excludes ROOT from the
coinbase loop (run_coinbase.rs:31), so RootClaimable should never carry
a ROOT entry and the asymmetry is invisible.

If anything ever inserts a ROOT entry though, RootClaimed would tick up
on every stake add but never drain on remove, since only the remove side
has the guard. Over enough cycles RootClaimed for the ROOT tuple would
exceed claimable and the existing saturating_sub in get_root_owed_* would
silently clamp owed to 0.

Mirror the guard in the add path and add a regression test that seeds
RootClaimable with both a ROOT entry and a normal netuid entry, calls
add_stake_adjust_root_claimed, and checks ROOT stays at 0 while the
normal entry is bumped by rate * amount.
@open-junius
Copy link
Copy Markdown
Contributor

RootClaimable can be inserted during run coinbase, which already exclulde the ROOT. could you specify any execution path "If anything ever inserts a ROOT entry though".

Per review: RootClaimable is never populated with a NetUid::ROOT key
under any current code path, so the original remove-side guard was
itself defensive against a case that can't happen. Mirroring that
guard onto the add side was chasing a ghost.

Do the other direction: drop the ROOT special-case from remove and
leave a block comment at the top of the iteration pair explaining
why RootClaimable never carries ROOT. `increase_root_claimable_for_hotkey_and_subnet`
is the only writer, called from `run_coinbase.rs:627` inside the
`netuid != NetUid::ROOT` loop (run_coinbase.rs:31).
`transfer_root_claimable_for_new_hotkey` and
`finalize_all_subnet_root_dividends` only propagate or remove existing
keys. Both functions now iterate the map uniformly.

Rewrite the test to pin the symmetric-iteration property: seed both
a ROOT entry and a regular entry into RootClaimable, call the add
path, then call the remove path with the same amount, and assert
both entries drain back to zero.
@boskodev790
Copy link
Copy Markdown
Author

You're right, I went back through the write sites and there isn't one. increase_root_claimable_for_hotkey_and_subnet is the only insert path, and its sole caller at run_coinbase.rs:627 sits inside the netuid != NetUid::ROOT loop set up at line 31. The other two writers (transfer_root_claimable_for_new_hotkey and finalize_all_subnet_root_dividends) only propagate or remove existing keys, so neither can introduce a ROOT key either. The remove-side guard was defensive against a case that can't actually happen today.

Flipped the approach in the latest commit: dropped the guard from the remove side too, left a block comment above the pair pointing at the invariant and the line that enforces it. The test is rewritten as a symmetric-iteration check — seed ROOT + a regular entry into RootClaimable, call add, call remove with the same amount, both drain back to zero. If a future writer ever does introduce a ROOT key, add and remove stay balanced instead of drifting.

cargo check --package pallet-subtensor --tests clean locally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants