1313
1414import click
1515from rich .console import Console
16+ from rich .table import Table
1617
1718from gittensor .constants import NETWORK_MAP
1819
1920console = Console ()
2021
2122NETUID_DEFAULT = 74
22-
23-
24- def _get_validator_axons (metagraph ) -> tuple [list , list ]:
25- """Return (axons, uids) for all active validators (vtrust > 0.1, serving)."""
26- axons = []
27- uids = []
23+ DEFAULT_MIN_VALIDATOR_VTRUST = 0.25
24+ DEFAULT_MIN_VALIDATOR_STAKE = 15_000.0
25+
26+
27+ def _get_validator_axons (
28+ metagraph ,
29+ * ,
30+ min_vtrust : float = DEFAULT_MIN_VALIDATOR_VTRUST ,
31+ min_stake : float = DEFAULT_MIN_VALIDATOR_STAKE ,
32+ ) -> tuple [list , list , list [dict ]]:
33+ """Return (axons, uids, excluded) for active validators.
34+
35+ A validator is broadcast to when vtrust > min_vtrust AND axon.is_serving
36+ AND stake >= min_stake. UIDs failing only the latter two checks are
37+ surfaced in `excluded` so miners can see why a high-vtrust validator
38+ was skipped. Sub-vtrust UIDs are dropped silently — they are not
39+ validators.
40+ """
41+ axons : list = []
42+ uids : list [int ] = []
43+ excluded : list [dict ] = []
2844 for uid in range (metagraph .n ):
29- if metagraph .validator_trust [uid ] > 0.1 and metagraph .axons [uid ].is_serving :
30- axons .append (metagraph .axons [uid ])
31- uids .append (uid )
32- return axons , uids
45+ vt = float (metagraph .validator_trust [uid ])
46+ if vt <= min_vtrust :
47+ continue
48+ serving = bool (metagraph .axons [uid ].is_serving )
49+ stake = float (metagraph .S [uid ])
50+ reasons : list [str ] = []
51+ if not serving :
52+ reasons .append ('not serving an axon' )
53+ if stake < min_stake :
54+ reasons .append (f'stake { stake :,.0f} α below { min_stake :,.0f} α threshold' )
55+ if reasons :
56+ excluded .append ({'uid' : uid , 'vtrust' : vt , 'stake' : stake , 'reasons' : reasons })
57+ continue
58+ axons .append (metagraph .axons [uid ])
59+ uids .append (uid )
60+ return axons , uids , excluded
3361
3462
3563def _load_config_value (key : str ):
@@ -96,13 +124,40 @@ def _require_registered(wallet, metagraph, netuid: int, json_mode: bool) -> None
96124 sys .exit (1 )
97125
98126
99- def _require_validator_axons (metagraph , json_mode : bool ) -> tuple [list , list ]:
100- """Return validator (axons, uids), or exit with error if none found."""
101- validator_axons , validator_uids = _get_validator_axons (metagraph )
127+ def _require_validator_axons (
128+ metagraph ,
129+ json_mode : bool ,
130+ * ,
131+ min_vtrust : float = DEFAULT_MIN_VALIDATOR_VTRUST ,
132+ min_stake : float = DEFAULT_MIN_VALIDATOR_STAKE ,
133+ ) -> tuple [list , list , list [dict ]]:
134+ """Return validator (axons, uids, excluded), or exit with error if no axons match."""
135+ validator_axons , validator_uids , excluded = _get_validator_axons (
136+ metagraph , min_vtrust = min_vtrust , min_stake = min_stake
137+ )
102138 if not validator_axons :
103139 _error ('No reachable validator axons found on the network.' , json_mode )
104140 sys .exit (1 )
105- return validator_axons , validator_uids
141+ return validator_axons , validator_uids , excluded
142+
143+
144+ def _render_skipped_validators (excluded : list [dict ], json_mode : bool ) -> None :
145+ """Print a 'Skipped Validators' table when any high-vtrust UIDs were filtered."""
146+ if json_mode or not excluded :
147+ return
148+ table = Table (title = 'Skipped Validators' )
149+ table .add_column ('UID' , style = 'cyan' , justify = 'right' )
150+ table .add_column ('vtrust' , justify = 'right' )
151+ table .add_column ('stake (α)' , justify = 'right' )
152+ table .add_column ('Reason' , style = 'dim' )
153+ for e in excluded :
154+ table .add_row (
155+ str (e ['uid' ]),
156+ f'{ e ["vtrust" ]:.4f} ' ,
157+ f'{ e ["stake" ]:,.0f} ' ,
158+ '; ' .join (e ['reasons' ]),
159+ )
160+ console .print (table )
106161
107162
108163def _pat_check_row_category (row : dict [str , Any ]) -> str :
@@ -125,3 +180,22 @@ def _pat_check_aggregate_counts(results: list[dict[str, Any]]) -> dict[str, int]
125180 'invalid_pat' : counts ['invalid_pat' ],
126181 'no_response' : counts ['no_response' ],
127182 }
183+
184+
185+ def _pat_post_row_category (row : dict [str , Any ]) -> str :
186+ """Classify a PAT broadcast row; must match `miner post` table rendering order."""
187+ if row .get ('accepted' ) is True :
188+ return 'accepted'
189+ if row .get ('accepted' ) is False :
190+ return 'rejected'
191+ return 'no_response'
192+
193+
194+ def _pat_post_aggregate_counts (results : list [dict [str , Any ]]) -> dict [str , int ]:
195+ """Count PAT broadcast rows by status for JSON summaries."""
196+ counts = Counter (_pat_post_row_category (r ) for r in results )
197+ return {
198+ 'accepted' : counts ['accepted' ],
199+ 'rejected' : counts ['rejected' ],
200+ 'no_response' : counts ['no_response' ],
201+ }
0 commit comments