Skip to content

Commit 385a54c

Browse files
authored
Merge branch 'test' into fix/issue-785-cache-shallow-copy
2 parents a8e2af6 + b9ab5c9 commit 385a54c

3 files changed

Lines changed: 66 additions & 9 deletions

File tree

gittensor/validator/forward.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async def forward(self: 'Validator') -> None:
6262
token_config = load_token_config()
6363

6464
# 1. Score OSS contributions
65-
oss_rewards, miner_evaluations, cached_uids = await oss_contributions(
65+
oss_rewards, miner_evaluations, cached_uids, penalized_uids = await oss_contributions(
6666
self, miner_uids, master_repositories, programming_languages, token_config
6767
)
6868

@@ -80,7 +80,7 @@ async def forward(self: 'Validator') -> None:
8080
# 5. Blend 4 emission pools into final rewards
8181
rewards = blend_emission_pools(oss_rewards, issue_rewards, miner_uids)
8282

83-
self.update_scores(rewards, miner_uids)
83+
self.update_scores(rewards, miner_uids, blacklisted_uids=sorted(penalized_uids))
8484

8585
await asyncio.sleep(VALIDATOR_WAIT)
8686

@@ -91,8 +91,8 @@ async def oss_contributions(
9191
master_repositories: Dict[str, RepositoryConfig],
9292
programming_languages: Dict,
9393
token_config,
94-
) -> Tuple[np.ndarray, Dict[int, MinerEvaluation], Set[int]]:
95-
"""Score OSS contributions and return normalized rewards + miner evaluations + cached UIDs.
94+
) -> Tuple[np.ndarray, Dict[int, MinerEvaluation], Set[int], Set[int]]:
95+
"""Score OSS contributions and return normalized rewards + miner evaluations + cached UIDs + penalized UIDs.
9696
9797
Pure scoring — no DB storage or emission blending. Those are handled by forward().
9898
"""
@@ -104,11 +104,11 @@ async def oss_contributions(
104104
bt.logging.info(f'Token config: {tree_sitter_count} tree-sitter languages')
105105
bt.logging.info(f'Neurons to evaluate: {len(miner_uids)}')
106106

107-
rewards, miner_evaluations, cached_uids = await get_rewards(
107+
rewards, miner_evaluations, cached_uids, penalized_uids = await get_rewards(
108108
self, miner_uids, master_repositories, programming_languages, token_config
109109
)
110110

111-
return rewards, miner_evaluations, cached_uids
111+
return rewards, miner_evaluations, cached_uids, penalized_uids
112112

113113

114114
async def issue_discovery(

gittensor/validator/oss_contributions/reward.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Copyright © 2025 Entrius
33
from __future__ import annotations
44

5-
from typing import TYPE_CHECKING, Dict, Optional, Tuple
5+
from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
66

77
import bittensor as bt
88
import numpy as np
@@ -99,11 +99,11 @@ async def get_rewards(
9999
master_repositories: Dict[str, RepositoryConfig],
100100
programming_languages: Dict[str, LanguageConfig],
101101
token_config: TokenConfig,
102-
) -> Tuple[np.ndarray, Dict[int, MinerEvaluation], set]:
102+
) -> Tuple[np.ndarray, Dict[int, MinerEvaluation], Set[int], Set[int]]:
103103
"""Score OSS contributions for all miners.
104104
105105
Returns:
106-
Tuple of (normalized_rewards_array, miner_evaluations, cached_uids).
106+
Tuple of (normalized_rewards_array, miner_evaluations, cached_uids, penalized_uids).
107107
DB storage and emission blending are handled by the caller (forward.py).
108108
"""
109109

@@ -160,4 +160,5 @@ async def get_rewards(
160160
np.array([normalized_rewards.get(uid, 0.0) for uid in sorted(uids)]),
161161
miner_evaluations,
162162
cached_uids,
163+
penalized_uids,
163164
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# The MIT License (MIT)
2+
# Copyright © 2025 Entrius
3+
4+
"""
5+
Regression test for #782: penalized_uids returned by
6+
detect_and_penalize_miners_sharing_github must reach update_scores as
7+
blacklisted_uids so prior EMA history for duplicate-account cheaters is
8+
wiped instead of bleeding through alpha-blending.
9+
"""
10+
11+
from typing import cast
12+
from unittest.mock import MagicMock
13+
14+
import numpy as np
15+
16+
from gittensor.classes import MinerEvaluation
17+
from gittensor.validator.oss_contributions.inspections import (
18+
detect_and_penalize_miners_sharing_github,
19+
)
20+
from neurons.base.validator import BaseValidatorNeuron
21+
22+
23+
class _DummyValidator:
24+
def __init__(self, scores: np.ndarray, alpha: float = 0.1):
25+
self.scores = scores.astype(float, copy=True)
26+
self.config = MagicMock()
27+
self.config.neuron.moving_average_alpha = alpha
28+
29+
30+
def test_detected_duplicates_wipe_prior_ema_via_update_scores():
31+
# Prior round: UID 1 and UID 2 posted PRs under the same GitHub account
32+
# and accumulated EMA weight. UID 0 is honest. On the unfixed call
33+
# pattern (no blacklisted_uids), cheater scores would decay only to
34+
# 0.9 * 0.45 = 0.405 via EMA instead of being zeroed.
35+
prior = np.array([0.1, 0.45, 0.45])
36+
validator = _DummyValidator(scores=prior, alpha=0.1)
37+
38+
evaluations = {
39+
0: MinerEvaluation(uid=0, hotkey='hotkey_0', github_id='gh_honest'),
40+
1: MinerEvaluation(uid=1, hotkey='hotkey_1', github_id='gh_shared'),
41+
2: MinerEvaluation(uid=2, hotkey='hotkey_2', github_id='gh_shared'),
42+
}
43+
44+
penalized_uids = detect_and_penalize_miners_sharing_github(evaluations)
45+
46+
BaseValidatorNeuron.update_scores(
47+
cast(BaseValidatorNeuron, validator),
48+
np.zeros(3),
49+
{0, 1, 2},
50+
blacklisted_uids=sorted(penalized_uids),
51+
)
52+
53+
assert validator.scores[1] == 0.0
54+
assert validator.scores[2] == 0.0
55+
assert validator.scores[0] > 0.0
56+
assert np.isclose(validator.scores.sum(), 1.0)

0 commit comments

Comments
 (0)