Skip to content

Commit baa4f9a

Browse files
anderdcanderdcMkDev11Nicknamess96bittoby
authored
Merge predictions v0 (#270)
Co-authored-by: anderdc <me@alexanderdc.com> Co-authored-by: MkDev11 <94194147+MkDev11@users.noreply.github.com> Co-authored-by: Nicknamess96 <113626193+Nicknamess96@users.noreply.github.com> Co-authored-by: BitToby <218712309+bittoby@users.noreply.github.com> Co-authored-by: bittoby <bittoby@users.noreply.github.com> Co-authored-by: Muhammet Eren Karakuş <erenkar950@gmail.com>
1 parent f974485 commit baa4f9a

60 files changed

Lines changed: 3445 additions & 1275 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ HOTKEY_NAME=default
1717
WANDB_API_KEY=
1818
# for issue bounties api calls
1919
GITTENSOR_VALIDATOR_PAT=
20-
# Optional custom name for wandb logging
20+
# Optional custom name for wandb logging
2121
WANDB_VALIDATOR_NAME=vali
2222

2323
# ******* MINER VARIABLES *******
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
## Weight Adjustment PR
2+
3+
### Changes Summary
4+
5+
| Metric | Gold | Silver | Bronze | Total |
6+
| -------------------- | ---- | ------ | ------ | ----- |
7+
| Repositories Added | 0 | 0 | 0 | 0 |
8+
| Repositories Removed | 0 | 0 | 0 | 0 |
9+
| Weights Modified | 0 | 0 | 0 | 0 |
10+
| Net Weight Change | 0 | 0 | 0 | 0 |
11+
12+
### Added Repositories
13+
14+
<!-- Delete this section if none -->
15+
16+
| Repository | Tier | Branch | Weight |
17+
| ---------- | ------ | ------ | ------ |
18+
| owner/repo | silver | main | 20.00 |
19+
20+
### Removed Repositories
21+
22+
<!-- Delete this section if none -->
23+
24+
| Repository | Tier | Reason |
25+
| ---------- | ------ | ------ |
26+
| owner/repo | silver ||
27+
28+
### Justification
29+
30+
<!-- Explain why these weight changes are being made -->
31+
32+
### Additional Acceptable Branches
33+
34+
<!--
35+
If this PR adds entries to additional_acceptable_branches for any repository,
36+
you MUST provide a link to a MERGED pull request in the target repository
37+
that demonstrates the contributor follows the workflow/pattern used in that repo.
38+
39+
Without this proof, the additional branch will not be approved.
40+
41+
Example:
42+
- `feature-branch` for `owner/repo`: https://github.com/owner/repo/pull/123 (merged)
43+
-->
44+
45+
- [ ] No additional_acceptable_branches changes in this PR
46+
- [ ] Proof of merged PR(s) provided above for all additional_acceptable_branches entries
47+
48+
### Checklist
49+
50+
- [ ] Changes summary table is filled in accurately
51+
- [ ] Net weight changes are justified in the Justification section
52+
- [ ] Added repositories have correct tier, branch, and initial weight

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ wandb
1616

1717
*.log
1818

19+
# Merge predictions local DB
20+
merge-prediction-data/
21+
gt-merge-preds.db
22+
gt-merge-preds.db-wal
23+
gt-merge-preds.db-shm
24+
1925
CLAUDE.md
2026
.claude/
2127
.vscode/

docker-compose.vali.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ services:
1212
volumes:
1313
# 'ro' = readonly
1414
- ${WALLET_PATH}:/root/.bittensor/wallets:ro
15+
- ./merge-prediction-data:/app/data
1516
# optional: uncomment this if you are running validator database
1617
# networks:
1718
# - gittensor_network

gittensor/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
# DEALINGS IN THE SOFTWARE.
1717

1818
# NOTE: bump this value when updating the codebase
19-
__version__ = '4.0.0'
19+
__version__ = '5.0.0'
2020
version_split = __version__.split('.')
2121
__spec_version__ = (1000 * int(version_split[0])) + (10 * int(version_split[1])) + (1 * int(version_split[2]))

gittensor/classes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from gittensor.constants import MIN_TOKEN_SCORE_FOR_BASE_SCORE
1212
from gittensor.utils.utils import parse_repo_name
13-
from gittensor.validator.configurations.tier_config import Tier, TierConfig, TierStats
13+
from gittensor.validator.oss_contributions.tier_config import Tier, TierConfig, TierStats
1414

1515
GITHUB_DOMAIN = 'https://github.com/'
1616

gittensor/cli/issue_commands/helpers.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import click
2222
from rich.console import Console
2323
from rich.panel import Panel
24+
from substrateinterface import SubstrateInterface
2425

2526
from gittensor.cli.issue_commands.tables import build_pr_table
2627
from gittensor.constants import CONTRACT_ADDRESS
@@ -43,7 +44,6 @@
4344
'Cancelled': 'dim',
4445
}
4546

46-
ISSUE_BOUNTY_ELIGIBLE_STATUSES = {'registered', 'active'}
4747

4848
# Default paths
4949
GITTENSOR_DIR = Path.home() / '.gittensor'
@@ -193,16 +193,21 @@ def print_issue_submission_table(
193193
console.print(f'Showing {len(pull_requests)} submissions{suffix}')
194194

195195

196-
def verify_miner_registration(ws_endpoint: str, contract_addr: str, hotkey_ss58: str) -> bool:
197-
"""Return whether the hotkey is registered on the subnet configured by the contract netuid."""
198-
import bittensor as bt
199-
from substrateinterface import SubstrateInterface
196+
def resolve_netuid_from_contract(ws_endpoint: str, contract_addr: str) -> Optional[int]:
197+
"""Read the subnet netuid stored in the on-chain contract."""
200198

201199
substrate = SubstrateInterface(url=ws_endpoint)
202200
packed = _read_contract_packed_storage(substrate, contract_addr)
203-
netuid = None
204201
if packed and packed.get('netuid') is not None:
205-
netuid = int(packed['netuid'])
202+
return int(packed['netuid'])
203+
return None
204+
205+
206+
def verify_miner_registration(ws_endpoint: str, contract_addr: str, hotkey_ss58: str) -> bool:
207+
"""Return whether the hotkey is registered on the subnet configured by the contract netuid."""
208+
import bittensor as bt
209+
210+
netuid = resolve_netuid_from_contract(ws_endpoint, contract_addr)
206211
if netuid is None:
207212
return False
208213

@@ -796,21 +801,18 @@ def fetch_issue_from_contract(
796801
ws_endpoint: str,
797802
contract_addr: str,
798803
issue_id: int,
799-
require_active: bool = False,
800804
verbose: bool = False,
801805
) -> Dict[str, Any]:
802-
"""Resolve an on-chain issue and validate bountied/active status."""
806+
"""Resolve an on-chain issue and validate bountied status."""
803807
issues = read_issues_from_contract(ws_endpoint, contract_addr, verbose)
804808
issue = next((i for i in issues if i.get('id') == issue_id), None)
805809
if not issue:
806810
raise click.ClickException(f'Issue ID {issue_id} not found on-chain.')
807811

808812
status = issue.get('status') or ''
809813
status_normalized = str(status).strip().lower()
810-
if status_normalized not in ISSUE_BOUNTY_ELIGIBLE_STATUSES:
814+
if status_normalized not in {'registered', 'active'}:
811815
raise click.ClickException(f'Issue #{issue_id} is not in a bountied state (status: {status}).')
812-
if require_active and status_normalized != 'active':
813-
raise click.ClickException(f'Issue #{issue_id} is not active (status: {status}).')
814816

815817
repo = issue.get('repository_full_name', '')
816818
issue_number = issue.get('issue_number', 0)

gittensor/cli/issue_commands/predict.py

Lines changed: 61 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import click
99

10+
from gittensor.miner.broadcast import broadcast_predictions
11+
1012
from .help import StyledCommand
1113
from .helpers import (
1214
_is_interactive,
@@ -24,6 +26,7 @@
2426
print_network_header,
2527
print_success,
2628
print_warning,
29+
resolve_netuid_from_contract,
2730
resolve_network,
2831
success_panel,
2932
validate_issue_id,
@@ -137,6 +140,10 @@ def issues_predict(
137140
ws_endpoint, network_name = resolve_network(network, rpc_url)
138141
effective_wallet, effective_hotkey = _resolve_wallet_identity(wallet_name, wallet_hotkey)
139142

143+
netuid = resolve_netuid_from_contract(ws_endpoint, contract_addr)
144+
if netuid is None:
145+
handle_exception(as_json, 'Could not resolve netuid from contract.')
146+
140147
if not as_json:
141148
print_network_header(network_name, contract_addr)
142149
console.print(f'Wallet: {effective_wallet}/{effective_hotkey}\n')
@@ -173,17 +180,7 @@ def issues_predict(
173180
print_warning('Prediction cancelled')
174181
return
175182

176-
# 7) Interactive mode: verify miner first to avoid wasting manual input.
177-
if is_interactive_mode:
178-
_resolve_registered_miner_hotkey(
179-
wallet_name=effective_wallet,
180-
wallet_hotkey=effective_hotkey,
181-
ws_endpoint=ws_endpoint,
182-
contract_addr=contract_addr,
183-
as_json=as_json,
184-
)
185-
186-
# 8) Collect predictions by mode; validate PR membership for non-interactive modes.
183+
# 7) Collect predictions by mode; validate PR membership for non-interactive modes.
187184
try:
188185
if is_interactive_mode:
189186
predictions = _collect_predictions_interactive(pull_requests)
@@ -193,39 +190,51 @@ def issues_predict(
193190
except (click.ClickException, click.BadParameter) as e:
194191
handle_exception(as_json, str(e))
195192

196-
# 9) Single/batch modes: verify miner after prediction payload validation.
197-
if not is_interactive_mode:
198-
_resolve_registered_miner_hotkey(
199-
wallet_name=effective_wallet,
200-
wallet_hotkey=effective_hotkey,
201-
ws_endpoint=ws_endpoint,
202-
contract_addr=contract_addr,
203-
as_json=as_json,
204-
)
205-
206-
payload = build_prediction_payload(
207-
issue_id=issue_id,
208-
repository=repo_full_name,
209-
predictions=predictions,
210-
)
211-
212-
# 10) Emit machine output or interactive confirmation flow.
213-
if as_json:
214-
emit_json(payload, pretty=True)
215-
broadcast_predictions_stub(payload)
216-
return
193+
payload = {
194+
'issue_id': issue_id,
195+
'repository': repo_full_name,
196+
'predictions': dict(predictions),
197+
'github_access_token': '***',
198+
}
217199

218-
if is_interactive_mode:
200+
# 8) Confirmation prompt (interactive only).
201+
if not as_json and is_interactive_mode:
219202
lines = format_prediction_lines(predictions)
220203
confirm_panel(lines, title='Prediction Confirmation')
221204
skip_confirm = yes or not _is_interactive()
222205
if not skip_confirm and not click.confirm('Proceed?', default=True):
223206
print_warning('Prediction cancelled')
224207
return
225208

226-
success_panel(json_mod.dumps(payload, indent=2), title='Prediction Payload')
227-
print_success('Prediction prepared (TODO: broadcast)')
228-
broadcast_predictions_stub(payload)
209+
# 9) Verify miner registration before broadcasting.
210+
_resolve_registered_miner_hotkey(
211+
wallet_name=effective_wallet,
212+
wallet_hotkey=effective_hotkey,
213+
ws_endpoint=ws_endpoint,
214+
contract_addr=contract_addr,
215+
as_json=as_json,
216+
)
217+
218+
# 10) Show payload and broadcast to validators.
219+
if as_json:
220+
emit_json(payload, pretty=True)
221+
222+
if not as_json:
223+
success_panel(json_mod.dumps(payload, indent=2), title='Prediction Synapse')
224+
225+
with loading_context('Broadcasting predictions to validators...', as_json):
226+
results = broadcast_predictions(
227+
payload=payload,
228+
wallet_name=effective_wallet,
229+
wallet_hotkey=effective_hotkey,
230+
ws_endpoint=ws_endpoint,
231+
netuid=netuid,
232+
)
233+
234+
if as_json:
235+
emit_json(results, pretty=True)
236+
else:
237+
_print_broadcast_results(results)
229238

230239

231240
def validate_probability(value: float, param_hint: str = 'probability') -> float:
@@ -285,9 +294,7 @@ def _resolve_issue_context(
285294
"""Load and validate on-chain issue context for prediction."""
286295
try:
287296
with loading_context('Reading issues from contract...', as_json):
288-
issue = fetch_issue_from_contract(
289-
ws_endpoint, contract_addr, issue_id, require_active=True, verbose=verbose
290-
)
297+
issue = fetch_issue_from_contract(ws_endpoint, contract_addr, issue_id, verbose=verbose)
291298
except click.ClickException as e:
292299
handle_exception(as_json, str(e))
293300

@@ -361,22 +368,22 @@ def format_prediction_lines(predictions: dict[int, float]) -> str:
361368
return '\n'.join(lines)
362369

363370

364-
def build_prediction_payload(
365-
issue_id: int,
366-
repository: str,
367-
predictions: dict[int, float],
368-
) -> dict[str, object]:
369-
"""Build validated payload for future network broadcast."""
370-
return {
371-
'issue_id': issue_id,
372-
'repository': repository,
373-
'predictions': dict(predictions),
374-
}
375-
371+
def _print_broadcast_results(results: dict[str, object]) -> None:
372+
"""Print broadcast results in human-readable format."""
373+
if results.get('error'):
374+
print_error(str(results['error']))
375+
return
376+
if results.get('success'):
377+
print_success(f'Prediction accepted by {results["accepted"]}/{results["total_validators"]} validator(s)')
378+
else:
379+
print_error(
380+
f'Prediction rejected or unreachable: {results["rejected"]}/{results["total_validators"]} validator(s)'
381+
)
376382

377-
def broadcast_predictions_stub(payload: dict[str, object]) -> None:
378-
"""Broadcast integration seam (stub)."""
379-
pass
383+
for r in results.get('results', []):
384+
status = 'accepted' if r['accepted'] else 'rejected'
385+
reason = f' ({r["rejection_reason"]})' if r.get('rejection_reason') else ''
386+
console.print(f' {r["validator"]}... {status}{reason}')
380387

381388

382389
def _collect_predictions_interactive(prs: list[dict]) -> dict[int, float]:

gittensor/cli/issue_commands/submissions.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,7 @@ def issues_submissions(
8383

8484
try:
8585
with loading_context('Fetching issue from contract...', as_json):
86-
issue = fetch_issue_from_contract(
87-
ws_endpoint, contract_addr, issue_id, require_active=False, verbose=verbose
88-
)
86+
issue = fetch_issue_from_contract(ws_endpoint, contract_addr, issue_id, verbose=verbose)
8987
except click.ClickException as e:
9088
handle_exception(as_json, str(e))
9189

0 commit comments

Comments
 (0)