src/api/handlers.rs— All route handlerssrc/storage/bounty_storage.rs— All storage operationssrc/scoring.rs— Weight & leaderboard calculationsrc/types.rs— Data structuressrc/validation.rs— Submission and issue validationsrc/lib.rs— Challenge trait implementation (evaluate, validate, get_weights)src/routes.rs— Route definitions and dispatch
| Key Pattern | Value Type | Used By |
|---|---|---|
user:{hotkey} |
UserRegistration (bincode) |
register_user, get_user_by_hotkey |
github:{lowercase_github} |
hotkey string (raw bytes) | register_user, get_hotkey_by_github |
issue:{owner}/{repo}:{issue_number_le_u32} |
IssueRecord (bincode) |
record_valid_issue, is_issue_recorded, get_issue_record |
invalid_issue:{owner}/{repo}:{issue_number_le_u32} |
InvalidIssueRecord (bincode) |
record_invalid_issue |
balance:{hotkey} |
UserBalance (bincode) |
get/store_user_balance, increment_valid/invalid_count |
leaderboard |
Vec<LeaderboardEntry> (bincode) |
get/store_leaderboard |
registered_hotkeys |
Vec<String> (bincode) |
get/add_registered_hotkey |
synced_issues |
Vec<IssueRecord> (bincode) |
store_issue_data, get_synced_issues |
active_miner_count |
u64 (LE bytes) | store/get_active_miner_count |
validator_count |
u64 (LE bytes) | store/get_validator_count |
No key collisions — all prefixes are distinct. Binary issue_number encoding in issue: and invalid_issue: keys is safely separated by the : delimiter from repo_name (GitHub names cannot contain :).
- Location:
src/api/handlers.rs, lines ~199-215 (legacy bincode path inhandle_claim) - Description: The legacy bincode
BountySubmissionpath extractsauth_hotkeyfrom headers but never verifies thatsubmission.hotkey == auth_hotkey. Theprocess_claimsfunction usessubmission.hotkey(from the untrusted body) for recording claims and incrementing balances. An attacker authenticating as hotkey A can submit aBountySubmissionwithhotkey: Band claim issues on behalf of B, or grief B by recording invalid claims. - Impact: Any authenticated user can claim issues attributed to any other hotkey.
- Location:
src/api/handlers.rs,handle_issues_sync - Description: The sync endpoint only checks
is_authenticated(request), which merely verifies a non-emptyauth_hotkey. There is no role check (validator, admin, etc.). Any miner/user with an auth token can call/issues/syncand completely replace thesynced_issuesdataset. - Impact: Attacker can inject fabricated issues (with
has_valid_label: true,is_closed: true,has_ide_label: true,author: attacker_github) into synced data, then immediately claim them via/claim. This enables unlimited self-awarded points. Alternatively, syncing an empty list DoS's all claim validation.
- Location:
src/api/handlers.rs,handle_issues_sync;src/storage/bounty_storage.rs,store_issue_data - Description: Synced issue data is deserialized and stored with zero validation. No checks on: issue_number validity, repo_owner/repo_name format, label consistency, author format, or whether the data matches any real GitHub state.
- Impact: Combined with C2, this is a complete bypass of the issue validation system.
- Location:
src/validation.rs,validate_submission;src/lib.rs,evaluate - Description:
validate_submissioncheckssubmission.signature.is_empty()to ensure a signature is present, but never verifies it against the hotkey or any public key. Theevaluatefunction also requires non-empty signature but never validates it. The JSON claim path bypasses signature entirely (sets empty vec). - Impact: The signature field provides zero security guarantee. Any non-empty byte array passes validation.
- Location:
src/storage/bounty_storage.rs,increment_valid_count(line ~220) - Description:
increment_valid_countonly incrementsvalid_countand stores the balance — it does NOT re-evaluateis_penalized. The penalty is only re-evaluated insideincrement_invalid_count. Once a user is penalized, claiming more valid issues increases theirvalid_countbut theis_penalizedflag remainstruepermanently (unless they also receive another invalid issue, which triggers re-evaluation). - Impact: Users who recover from a penalized state (by getting more valid issues than invalid) remain stuck as penalized with zero weight forever.
- Location:
src/types.rs(line 69),src/storage/bounty_storage.rs(line 232) - Description:
UserBalance.duplicate_countis declared and used in the penalty formula:penalty = (invalid_count - valid_count) + (duplicate_count - valid_count), butduplicate_countis never incremented anywhere in the codebase. It is always 0. - Impact: The penalty formula's duplicate_count component is dead code. The actual penalty reduces to just
invalid_count > valid_count.
- Location:
src/storage/bounty_storage.rs,increment_invalid_count(lines ~228-233) - Description: The penalty formula is:
penalty = (invalid_count - valid_count).saturating + (duplicate_count - valid_count).saturating. This subtractsvalid_countseparately from bothinvalid_countandduplicate_count. This meansvalid_countacts as an independent buffer against each type of infraction rather than a shared buffer. Example: if valid=5, invalid=4, duplicate=4 → penalty = 0+0 = 0 (not penalized). But total infractions (8) exceed valid (5). - Impact: Currently moot because
duplicate_countis always 0, but ifduplicate_countis ever used, the penalty logic will be more lenient than likely intended.
- Location:
src/scoring.rs,rebuild_leaderboard(line ~84) vscalculate_weights_from_leaderboard(line ~38) - Description:
rebuild_leaderboardsorts entries byscorewhich is(valid_count + star_count * 0.25) * 0.02— ignoring invalid issues. Butcalculate_weights_from_leaderboarddistributes weights based onnet_pointswhich subtracts penalties. A user ranked #1 on the leaderboard (byscore) could receive less weight than a lower-ranked user (bynet_points). - Impact: The displayed leaderboard ranking does not correspond to actual weight distribution. Misleading to users.
- Location:
src/api/handlers.rs,handle_register;src/storage/bounty_storage.rs,register_user - Description: Neither
handle_registernorregister_uservalidates thatgithub_usernameis non-empty. An empty github_username creates storage entries atuser:{hotkey}with empty github andgithub:(prefix with empty suffix). Thegithub:key could collide with future uses or interfere with lookups. - Impact: Allows registration with empty github username. The user would never be able to claim issues (author matching would fail), but pollutes storage and the registered_hotkeys list.
- Location:
src/api/handlers.rs,handle_claim(legacy bincode path) - Description: The legacy path calls
validate_submission(checks non-empty fields) thenprocess_claims, but never checks thatsubmission.github_usernamematches the github registered forsubmission.hotkey. This is checked inevaluate()but not inhandle_claim. Inprocess_claims→validate_issue, theexpected_authorissubmission.github_usernamewhich is user-supplied. - Impact: A user could set
github_usernameto any value in the submission, bypassing author verification for issues authored by that github user.
- Location:
src/validation.rs,process_claims - Description:
process_claimsdirectly callsrecord_valid_issuewithsubmission.hotkeywithout checking if it's a registered user.record_valid_issueincrementsbalance:{hotkey}even for unregistered hotkeys. These unregistered hotkeys won't appear on the leaderboard (since they're not inregistered_hotkeys), but their balance data accumulates silently. - Impact: Phantom balance entries for unregistered hotkeys. If the hotkey registers later, those balances are retroactively picked up.
- Location:
src/storage/bounty_storage.rs,register_user - Description: If a hotkey re-registers with the same github username (valid re-registration), the
registered_epochis overwritten with the current epoch. This loses the original registration timestamp. - Impact: Minor data loss — original registration date is lost on re-registration.
- Location:
src/api/handlers.rs,handle_claim(JSON path, lines ~170-185) - Description: The URL is split by
/and onlyparts.len() >= 7is checked. There is no validation thatparts[2]isgithub.comor thatparts[5]isissues. A URL likehttps://evil.com/owner/repo/pulls/123would pass parsing and be treated as a valid issue claim. The claim would likely fail because the issue wouldn't exist in synced data, but the parsing is over-permissive. - Impact: Low — the issue must still match synced data. But opens door to confusion or exploits if synced data is manipulated (see C2/C3).
- Location:
src/storage/bounty_storage.rs,record_valid_issue(lines ~108-120) - Description: When recording a claimed issue, properties like
is_closed: true,has_valid_label: true,has_ide_label: trueare hardcoded rather than copied from the actual synced issue data. The stored claim record doesn't reflect the actual issue state at claim time. - Impact: Minor — the values represent required conditions for a valid claim, so they're correct by construction. But if requirements change, these would need manual updating.
- Location:
src/api/handlers.rs,handle_statusandhandle_hotkey_details - Description: The
weightfield is computed ascalculate_weight_from_points(valid_count, star_count)which returns an absolute value (not normalized 0-1). Meanwhile,/get_weightsreturns normalized weights that sum to 1.0. The two "weight" concepts are different but use the same name. - Impact: API consumers may confuse the raw weight in
/status/:hotkeywith the normalized weight from/get_weights. Documentation/naming concern.
- Location:
src/storage/bounty_storage.rs,store_issue_data/get_synced_issues - Description: All synced issues are serialized and stored as a single key-value pair. Every read deserializes the entire collection. Every sync replaces the entire collection atomically.
- Impact: Scalability concern as issue count grows. Not a correctness issue.
- Location:
src/storage/bounty_storage.rs,register_user - Description: Registration writes two keys:
user:{hotkey}andgithub:{username}. If the first write succeeds but the second fails, the user record exists but the github→hotkey reverse mapping does not. The function returnsfalseif the second write fails, but the first write is not rolled back. - Impact: Inconsistent state on partial failure — the user appears registered by hotkey but not findable by github username. Depends on host storage reliability.
- Location:
src/storage/bounty_storage.rs,get_pending_issues - Description: Pending issues are defined as
!i.is_closed && i.claimed_by_hotkey.is_none(). But inhandle_issues_stats, "pending" is defined asi.is_closed && !i.has_valid_label && !i.has_invalid_label(closed but not yet reviewed). These two definitions of "pending" are inconsistent. - Impact: The
/issues/pendingendpoint and thependingcount in/issues/statsshow different things under the same name.
- Auth check → deserialize
RegisterRequest→ extract hotkey from auth header (fallback to body) register_user: checks 1-to-1 mapping (github↔hotkey), storesuser:{hotkey}+github:{name}ensure_hotkey_tracked: adds hotkey toregistered_hotkeyslist
Gaps: No github_username format/emptiness validation. No rate limiting.
- Auth check (any non-empty auth_hotkey) → deserialize
Vec<IssueRecord>→ store atsynced_issues
Gaps: No authorization (anyone can sync). No data validation. Complete overwrite.
- Auth check → parse JSON
ClaimRequest(or legacyBountySubmission) - JSON path: parse URL, lookup github from auth_hotkey, build
BountySubmission - Legacy path: deserialize from body (hotkey from body, not verified against auth)
process_claims: for each issue_number: checkis_issue_recorded→ find in synced_issues →validate_issue(closed, ide label, valid label, not invalid, author match, not claimed) →record_valid_issue(stores atissue:...key, incrementsbalance:{hotkey}.valid_count)- If any issues claimed →
rebuild_leaderboard
Gaps: Legacy path doesn't verify body hotkey == auth hotkey. Legacy path doesn't verify body github == registered github. No registration check in process_claims.
- Iterate all
registered_hotkeys→ getUserBalance→ computenet_pointsandscore - Sort by
score(ignores penalties) → assign ranks → store atleaderboard
Gaps: Sorted by score (not net_points). Stale is_penalized flag used.
- Filter:
!is_penalized && net_points > 0.0 - Normalize: each weight =
net_points / total_net_points
Logic is correct given accurate input data. Issues stem from stale is_penalized and leaderboard sorting inconsistency upstream.