You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[Bug] _PR_TIMELINE_QUERY's closingIssuesReferences fragment omits repository field — cross-repo number collisions mis-attribute issue-bounty solvers and pay the wrong miner #1041
There are twoclosingIssuesReferences GraphQL fragments in gittensor/utils/github_api_tools.py:
Line 91 — inside the main QUERY used by load_miners_prs for OSS-scoring. Open #1019 covers this site, with a fix in #1020 that adds repository { nameWithOwner } and drops nodes whose repo doesn't match.
Line 426 — inside _PR_TIMELINE_QUERY used by _search_issue_referencing_prs_graphql → find_solver_from_cross_references → check_github_issue_closed, which feeds the issue-bounty voting forward pass at gittensor/validator/issue_competitions/forward.py:90.
The fix in #1020 only touches site (1). Site (2) still emits nodes { number } without repository qualification, so the downstream solver lookup in find_solver_from_cross_references matches by issue number alone and can credit a PR that closes a same-numbered issue in a different repository as the solver of the bounty.
Set up a bounty for entrius/repo-A#42. A PR entrius/repo-A#100 is in repo-A's timeline (e.g. as a CROSS_REFERENCED_EVENT — body mentions repo-A#42 as related context), but its actual closing reference points to other-org/repo-B#42 via the cross-repo close syntax Closes other-org/repo-B#42.
_search_issue_referencing_prs_graphql('entrius/repo-A', 42, ...) returns PR Fix: check validator thread in main loop #100 in its result list — baseRepository = entrius/repo-A passes the line 491 filter.
The PR's closingIssuesReferences.nodes returns [{number: 42}] — the GraphQL fragment didn't include the repo, so the parser sees just the bare number.
closing_numbers = [42]. The downstream filter 42 in [42] matches.
find_solver_from_cross_references returns (author_id_of_PR_100, 100) as the canonical solver of entrius/repo-A#42.
Real ALPHA payout to the wrong recipient. Like find_solver_from_cross_references's null-merged_at failure mode, this fires inside the on-chain consensus path; a wrong vote is irreversible once enough validators agree.
Asymmetry with #1019 / #1020. That fix patches the OSS-scoring path's GraphQL fragment but leaves the bounty-voting path's identical-shape vulnerability open. The two fragments share the same name (closingIssuesReferences) but live in two distinct queries, so it's easy to miss the second.
Same root cause, broader consequence. OSS scoring caps individual miner inflation at the multiplier level. Bounty voting transfers ALPHA on consensus.
This way closing_numbers only contains issue numbers whose recorded repository matches the issue we're resolving the solver for. Backwards-compat is preserved (existing tests exercise same-repo PRs whose closingIssuesReferences[*].repository.nameWithOwner will match).
GraphQL constant (_PR_TIMELINE_QUERY): no prior issue or PR mentions this query name in connection with cross-repo handling.
Clean across all three.
Test gap
No existing test in tests/utils/ (or anywhere) constructs a fixture where _PR_TIMELINE_QUERY's closingIssuesReferences returns an issue from a different repo than the one being resolved. The fix should add such a fixture and assert that find_solver_from_cross_references('repo-A', 42, …) returns (None, None) when the only matching candidate's closing_numbers entry actually belongs to repo-B#42.
Description:
There are two
closingIssuesReferencesGraphQL fragments ingittensor/utils/github_api_tools.py:Line 91 — inside the main
QUERYused byload_miners_prsfor OSS-scoring. Open #1019 covers this site, with a fix in #1020 that addsrepository { nameWithOwner }and drops nodes whose repo doesn't match.Line 426 — inside
_PR_TIMELINE_QUERYused by_search_issue_referencing_prs_graphql→find_solver_from_cross_references→check_github_issue_closed, which feeds the issue-bounty voting forward pass atgittensor/validator/issue_competitions/forward.py:90.The fix in #1020 only touches site (1). Site (2) still emits
nodes { number }without repository qualification, so the downstream solver lookup infind_solver_from_cross_referencesmatches by issue number alone and can credit a PR that closes a same-numbered issue in a different repository as the solver of the bounty.Source
gittensor/utils/github_api_tools.py:418-449—_PR_TIMELINE_QUERY:gittensor/utils/github_api_tools.py:502-505— the parser drops the repo information that GraphQL would have provided:gittensor/utils/github_api_tools.py:1005— solver candidate filter relies on the bare number list:Reproduction
Set up a bounty for
entrius/repo-A#42. A PRentrius/repo-A#100is inrepo-A's timeline (e.g. as a CROSS_REFERENCED_EVENT — body mentionsrepo-A#42as related context), but its actual closing reference points toother-org/repo-B#42via the cross-repo close syntaxCloses other-org/repo-B#42._search_issue_referencing_prs_graphql('entrius/repo-A', 42, ...)returns PR Fix: check validator thread in main loop #100 in its result list —baseRepository = entrius/repo-Apasses the line 491 filter.closingIssuesReferences.nodesreturns[{number: 42}]— the GraphQL fragment didn't include the repo, so the parser sees just the bare number.closing_numbers = [42]. The downstream filter42 in [42]matches.find_solver_from_cross_referencesreturns(author_id_of_PR_100, 100)as the canonical solver ofentrius/repo-A#42.check_github_issue_closedreturnssolver_lookup_failed=False, solver_github_id=author_of_100, pr_number=100.forward.py:149-155callsvote_solution(issue_id, hotkey_for(author_of_100), coldkey_for(author_of_100), pr_number=100, …).entrius/repo-A#42.Why this matters
find_solver_from_cross_references's null-merged_atfailure mode, this fires inside the on-chain consensus path; a wrong vote is irreversible once enough validators agree.closingIssuesReferences) but live in two distinct queries, so it's easy to miss the second.Suggested fix
Mirror #1020's fix on the second fragment:
Then in the parser at
_search_issue_referencing_prs_graphql:This way
closing_numbersonly contains issue numbers whose recorded repository matches the issue we're resolving the solver for. Backwards-compat is preserved (existing tests exercise same-repo PRs whoseclosingIssuesReferences[*].repository.nameWithOwnerwill match).3-axis prior-art
find_solver_from_cross_references/_search_issue_referencing_prs_graphql): only [Bug] find_solver_from_cross_references credits wrong solver on multi-PR closing references - latest-merged PR steals bounty #757, fix: credit earliest-merged PR in find_solver_from_cross_references #758, [Bug]find_solver_from_cross_referencessorts merged PRs by ISO string instead of parsed datetime #881, fix: sort solver candidates by parsed merged_at, not raw ISO string #882, [Bug] Issue-bounty solver lookup uses first: 50 chrono-ASC; popular issues miss the solver PR and the validator votes cancel #1017, fix: fetch newest cross-references for solver lookup so popular issues find the merged PR #1018 — none touch the cross-repoclosingIssuesReferencesgap._PR_TIMELINE_QUERY): no prior issue or PR mentions this query name in connection with cross-repo handling.Clean across all three.
Test gap
No existing test in
tests/utils/(or anywhere) constructs a fixture where_PR_TIMELINE_QUERY'sclosingIssuesReferencesreturns an issue from a different repo than the one being resolved. The fix should add such a fixture and assert thatfind_solver_from_cross_references('repo-A', 42, …)returns(None, None)when the only matching candidate's closing_numbers entry actually belongs torepo-B#42.