Skip to content

Bug: Penalized miners incorrectly contribute to unique_repos count in dynamic emissions #65

Description

@NikolaPantel

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

  1. Fairness: Penalized miners shouldn't influence emission distribution
  2. Consistency: Both metrics should use the same filtering logic
  3. Gaming prevention: Prevents miners from creating duplicate accounts just to inflate repo counts
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions