Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions gittensor/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,10 @@ class MinerEvaluation:
total_leaf_score: float = 0.0
failed_reason: Optional[str] = None
github_pr_fetch_failed: bool = False
# Mirror-source-specific fetch flag set by mirror.combine.combine alongside
# the OR into github_pr_fetch_failed. Lets the validator tell a complete
# mirror outage apart from a legacy partial-pagination failure.
mirror_pr_fetch_failed: bool = False
evaluation_timestamp: Optional[datetime] = None
merged_pull_requests: List[PullRequest] = field(default_factory=list)
open_pull_requests: List[PullRequest] = field(default_factory=list)
Expand Down
4 changes: 4 additions & 0 deletions gittensor/validator/oss_contributions/mirror/combine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
- Mirror PR lists land in the ``mirror_*`` slots on the MinerEvaluation
- ``unique_repos_contributed_to`` is unioned
- ``github_pr_fetch_failed`` is OR'd
- ``mirror_pr_fetch_failed`` is materialized so the validator can tell
a complete mirror outage apart from a legacy partial-pagination failure
when deciding cache fallback strategy

Per-PR scoring breakdowns (token_score, nodes_scored, base_score, earned_score,
collateral_score) live on each ScoredMirrorPR — they get aggregated into
Expand All @@ -27,4 +30,5 @@ def combine(legacy_eval: MinerEvaluation, mirror_eval: MirrorMinerEvaluation) ->

legacy_eval.unique_repos_contributed_to |= mirror_eval.unique_repos_contributed_to

legacy_eval.mirror_pr_fetch_failed = mirror_eval.fetch_failed
legacy_eval.github_pr_fetch_failed = legacy_eval.github_pr_fetch_failed or mirror_eval.fetch_failed
7 changes: 6 additions & 1 deletion neurons/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ def store_or_use_cached_evaluation(self, miner_evaluations: Dict[int, MinerEvalu
Handle evaluation cache: store successful evals, fallback to cache for GitHub failures.

Mutates the passed dict, replacing failed evaluations with cached ones if available.
A mirror-only fetch failure also routes through the cache-fallback path so the miner
is evaluated against a coherent one-round-stale snapshot instead of a fresh-legacy +
zeroed-mirror view where cross-PR multipliers would recompute over a partial state.

Returns:
Set of UIDs that were restored from cache (should be skipped during DB storage
Expand All @@ -161,7 +164,9 @@ def store_or_use_cached_evaluation(self, miner_evaluations: Dict[int, MinerEvalu
self.evaluation_cache.store(miner_eval)
continue

if not miner_eval.should_use_cache_fallback:
# Legacy partial-pagination failure with no mirror outage: the current eval
# holds a truncated legacy PR list that would be misleading to cache or swap out.
if not miner_eval.should_use_cache_fallback and not miner_eval.mirror_pr_fetch_failed:
bt.logging.warning(
f'UID {uid}: GitHub fetch failed after partial PR load; skipping cache store/fallback this round'
)
Expand Down
4 changes: 4 additions & 0 deletions tests/validator/oss_contributions/mirror/test_combine.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,22 +133,26 @@ def test_legacy_failed_only(self):
mirror_eval = MirrorMinerEvaluation(uid=1, hotkey='hk')
combine(legacy, mirror_eval)
assert legacy.github_pr_fetch_failed is True
assert legacy.mirror_pr_fetch_failed is False

def test_mirror_failed_only(self):
legacy = MinerEvaluation(uid=1, hotkey='hk')
mirror_eval = MirrorMinerEvaluation(uid=1, hotkey='hk', fetch_failed=True)
combine(legacy, mirror_eval)
assert legacy.github_pr_fetch_failed is True
assert legacy.mirror_pr_fetch_failed is True

def test_both_failed(self):
legacy = MinerEvaluation(uid=1, hotkey='hk')
legacy.github_pr_fetch_failed = True
mirror_eval = MirrorMinerEvaluation(uid=1, hotkey='hk', fetch_failed=True)
combine(legacy, mirror_eval)
assert legacy.github_pr_fetch_failed is True
assert legacy.mirror_pr_fetch_failed is True

def test_neither_failed(self):
legacy = MinerEvaluation(uid=1, hotkey='hk')
mirror_eval = MirrorMinerEvaluation(uid=1, hotkey='hk')
combine(legacy, mirror_eval)
assert legacy.github_pr_fetch_failed is False
assert legacy.mirror_pr_fetch_failed is False
32 changes: 31 additions & 1 deletion tests/validator/test_validator_cache_fallback.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ def _make_pr(uid: int) -> PullRequest:
)


def _build_eval(uid: int, merged_prs: int, fetch_failed: bool) -> MinerEvaluation:
def _build_eval(
uid: int,
merged_prs: int,
fetch_failed: bool,
mirror_pr_fetch_failed: bool = False,
) -> MinerEvaluation:
eval_ = MinerEvaluation(uid=uid, hotkey='hotkey_1', github_id='12345')
eval_.merged_pull_requests = [_make_pr(uid) for _ in range(merged_prs)]
eval_.github_pr_fetch_failed = fetch_failed
eval_.mirror_pr_fetch_failed = mirror_pr_fetch_failed
return eval_


Expand Down Expand Up @@ -83,6 +89,30 @@ def test_fetch_failure_after_partial_load_skips_cache_store_and_fallback(self):
assert cached_eval.total_prs == 2


class TestMirrorFailureCacheFallback:
"""A mirror outage routes the miner through the cache-fallback path so the
evaluation is a coherent one-round-stale snapshot rather than a fresh-legacy +
zeroed-mirror hybrid where cross-PR multipliers recompute over a partial view."""

def test_mirror_failure_with_legacy_success_swaps_to_cached_eval(self):
validator = _DummyValidator()
validator.evaluation_cache.store(_build_eval(uid=1, merged_prs=2, fetch_failed=False))

current_eval = _build_eval(
uid=1,
merged_prs=1,
fetch_failed=True,
mirror_pr_fetch_failed=True,
)
miner_evaluations = {1: current_eval}

cached_uids = Validator.store_or_use_cached_evaluation(cast(Validator, validator), miner_evaluations)

assert cached_uids == {1}
assert miner_evaluations[1] is not current_eval
assert miner_evaluations[1].total_prs == 2


class TestCacheIsolation:
"""Lock down the invariants required by the non-deepcopy copy strategy:
caller mutations must not leak into the cache, and heavy file_changes
Expand Down
Loading