Bug: Penalized miners incorrectly contribute to unique_repos count in dynamic emissions
Summary
In dynamic_emissions.py, the _get_network_totals() function has inconsistent logic: penalized miners (score = 0) are excluded from total_lines but included in unique_repos count. This inflates the repo scalar and reduces recycling unfairly.
Severity
🟡 MEDIUM - Affects emission distribution but doesn't crash the system
Location
- File:
gittensor/validator/evaluation/dynamic_emissions.py
- Function:
_get_network_totals()
- Lines: 26-30
Current Code (BUGGY):
def _get_network_totals(miner_evaluations: Dict[int, MinerEvaluation]) -> tuple[int, int]:
"""Extract total lines changed and unique repos from evaluations."""
total_lines = 0
unique_repos: Set[str] = set()
for evaluation in miner_evaluations.values():
if evaluation.total_score > 0: # Exclude penalized miners
total_lines += evaluation.total_lines_changed
if repos := evaluation.unique_repos_contributed_to: # ❌ NO SCORE CHECK!
unique_repos.update(repos)
return total_lines, len(unique_repos)
Problem
Inconsistent handling of penalized miners:
- ✅ Line 27-28: Penalized miners (score = 0) are excluded from
total_lines
- ❌ Line 29-30: Penalized miners are included in
unique_repos count
Penalized miners include:
- Duplicate accounts (detected by
detect_and_penalize_duplicates)
- Invalid GitHub PATs
- Accounts too young (< MIN_GITHUB_ACCOUNT_AGE)
- UID 0 (RECYCLE_UID)
Impact
1. Inflated Repo Scalar
# Example scenario:
# Miner 1: Valid, 100 lines, 5 repos, score = 50
# Miner 2: Penalized (duplicate), 0 lines, 10 repos, score = 0
# Current behavior (BUGGY):
total_lines = 100 # ✅ Only Miner 1
unique_repos = 15 # ❌ Both miners (5 + 10)
repo_scalar = higher # Inflated by penalized miner's repos
# Expected behavior:
total_lines = 100 # ✅ Only Miner 1
unique_repos = 5 # ✅ Only Miner 1
repo_scalar = correct # Not inflated
2. Reduced Recycling
Higher repo_scalar → higher final_scalar → less recycling:
final_scalar = (lines_scalar + repo_scalar) / 2.0
total_recycled = total_original * (1 - final_scalar)
If repo_scalar is artificially inflated by penalized miners:
final_scalar increases
total_recycled decreases
- Less emissions go to RECYCLE_UID
- Valid miners get slightly more (unfair advantage)
3. Inconsistency with Design Intent
The code explicitly checks evaluation.total_score > 0 for total_lines, indicating penalized miners should be excluded. The same logic should apply to unique_repos.
Proposed Fix
Move the unique_repos update inside the score check:
def _get_network_totals(miner_evaluations: Dict[int, MinerEvaluation]) -> tuple[int, int]:
"""Extract total lines changed and unique repos from evaluations."""
total_lines = 0
unique_repos: Set[str] = set()
for evaluation in miner_evaluations.values():
if evaluation.total_score > 0: # Exclude penalized miners
total_lines += evaluation.total_lines_changed
if repos := evaluation.unique_repos_contributed_to:
unique_repos.update(repos)
return total_lines, len(unique_repos)
Why This Matters
- Fairness: Penalized miners shouldn't influence emission distribution
- Consistency: Both metrics should use the same filtering logic
- Gaming prevention: Prevents miners from creating duplicate accounts just to inflate repo counts
- Correct incentives: Only valid contributions should affect network-wide scalars
Testing
# Test case 1: Penalized miner with repos
miner1 = MinerEvaluation(uid=1, total_score=50, total_lines_changed=100, unique_repos_contributed_to={'repo1', 'repo2'})
miner2 = MinerEvaluation(uid=2, total_score=0, total_lines_changed=0, unique_repos_contributed_to={'repo3', 'repo4'})
evaluations = {1: miner1, 2: miner2}
total_lines, unique_repos_count = _get_network_totals(evaluations)
# Expected (FIXED):
assert total_lines == 100
assert unique_repos_count == 2 # Only miner1's repos
# Current (BUGGY):
# total_lines == 100 ✅
# unique_repos_count == 4 ❌ (includes miner2's repos)
Related Code
detect_and_penalize_duplicates() in inspections.py - sets score to 0
validate_response_and_initialize_miner_evaluation() in inspections.py - sets failed_reason
apply_dynamic_emissions_using_network_contributions() - uses the buggy totals
Additional Notes
This is a logic consistency bug rather than a crash bug, but it affects the fairness of emission distribution. The fix is simple (1 line indentation change) but important for maintaining system integrity.
@LandynDev I can submit a PR with the fix if this is confirmed as a valid issue.
Bug: Penalized miners incorrectly contribute to unique_repos count in dynamic emissions
Summary
In
dynamic_emissions.py, the_get_network_totals()function has inconsistent logic: penalized miners (score = 0) are excluded fromtotal_linesbut included inunique_reposcount. This inflates the repo scalar and reduces recycling unfairly.Severity
🟡 MEDIUM - Affects emission distribution but doesn't crash the system
Location
gittensor/validator/evaluation/dynamic_emissions.py_get_network_totals()Current Code (BUGGY):
Problem
Inconsistent handling of penalized miners:
total_linesunique_reposcountPenalized miners include:
detect_and_penalize_duplicates)Impact
1. Inflated Repo Scalar
2. Reduced Recycling
Higher
repo_scalar→ higherfinal_scalar→ less recycling:If
repo_scalaris artificially inflated by penalized miners:final_scalarincreasestotal_recycleddecreases3. Inconsistency with Design Intent
The code explicitly checks
evaluation.total_score > 0fortotal_lines, indicating penalized miners should be excluded. The same logic should apply tounique_repos.Proposed Fix
Move the
unique_reposupdate inside the score check:Why This Matters
Testing
Related Code
detect_and_penalize_duplicates()ininspections.py- sets score to 0validate_response_and_initialize_miner_evaluation()ininspections.py- sets failed_reasonapply_dynamic_emissions_using_network_contributions()- uses the buggy totalsAdditional Notes
This is a logic consistency bug rather than a crash bug, but it affects the fairness of emission distribution. The fix is simple (1 line indentation change) but important for maintaining system integrity.
@LandynDev I can submit a PR with the fix if this is confirmed as a valid issue.