bittensor-cli 9.0.0rc4__py3-none-any.whl → 9.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bittensor_cli/__init__.py +2 -3
- bittensor_cli/cli.py +254 -99
- bittensor_cli/src/__init__.py +50 -25
- bittensor_cli/src/bittensor/balances.py +1 -1
- bittensor_cli/src/bittensor/chain_data.py +20 -8
- bittensor_cli/src/bittensor/extrinsics/registration.py +26 -34
- bittensor_cli/src/bittensor/extrinsics/root.py +5 -11
- bittensor_cli/src/bittensor/extrinsics/transfer.py +15 -13
- bittensor_cli/src/bittensor/subtensor_interface.py +20 -20
- bittensor_cli/src/bittensor/utils.py +129 -19
- bittensor_cli/src/commands/stake/add.py +4 -8
- bittensor_cli/src/commands/stake/children_hotkeys.py +13 -19
- bittensor_cli/src/commands/stake/list.py +44 -77
- bittensor_cli/src/commands/stake/move.py +3 -3
- bittensor_cli/src/commands/stake/remove.py +216 -101
- bittensor_cli/src/commands/subnets/subnets.py +147 -5
- bittensor_cli/src/commands/sudo.py +192 -70
- bittensor_cli/src/commands/wallets.py +102 -52
- bittensor_cli/src/commands/weights.py +9 -13
- bittensor_cli/version.py +18 -0
- {bittensor_cli-9.0.0rc4.dist-info → bittensor_cli-9.0.2.dist-info}/METADATA +3 -3
- bittensor_cli-9.0.2.dist-info/RECORD +35 -0
- bittensor_cli-9.0.0rc4.dist-info/RECORD +0 -34
- {bittensor_cli-9.0.0rc4.dist-info → bittensor_cli-9.0.2.dist-info}/WHEEL +0 -0
- {bittensor_cli-9.0.0rc4.dist-info → bittensor_cli-9.0.2.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-9.0.0rc4.dist-info → bittensor_cli-9.0.2.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING, Union, Optional
|
|
3
3
|
|
4
4
|
import typer
|
5
5
|
from bittensor_wallet import Wallet
|
6
|
-
from bittensor_wallet.errors import KeyFileError
|
7
6
|
from rich import box
|
8
7
|
from rich.table import Column, Table
|
9
8
|
from rich.prompt import Confirm
|
@@ -17,6 +16,10 @@ from bittensor_cli.src.bittensor.utils import (
|
|
17
16
|
print_error,
|
18
17
|
print_verbose,
|
19
18
|
normalize_hyperparameters,
|
19
|
+
unlock_key,
|
20
|
+
blocks_to_duration,
|
21
|
+
float_to_u64,
|
22
|
+
float_to_u16,
|
20
23
|
)
|
21
24
|
|
22
25
|
if TYPE_CHECKING:
|
@@ -69,12 +72,76 @@ def allowed_value(
|
|
69
72
|
return True, value
|
70
73
|
|
71
74
|
|
75
|
+
def search_metadata(
|
76
|
+
param_name: str, value: Union[str, bool, float, list[float]], netuid: int, metadata
|
77
|
+
) -> tuple[bool, Optional[dict]]:
|
78
|
+
"""
|
79
|
+
Searches the substrate metadata AdminUtils pallet for a given parameter name. Crafts a response dict to be used
|
80
|
+
as call parameters for setting this hyperparameter.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
param_name: the name of the hyperparameter
|
84
|
+
value: the value to set the hyperparameter
|
85
|
+
netuid: the specified netuid
|
86
|
+
metadata: the subtensor.substrate.metadata
|
87
|
+
|
88
|
+
Returns:
|
89
|
+
(success, dict of call params)
|
90
|
+
|
91
|
+
"""
|
92
|
+
|
93
|
+
def string_to_bool(val) -> bool:
|
94
|
+
try:
|
95
|
+
return {"true": True, "1": True, "0": False, "false": False}[val.lower()]
|
96
|
+
except KeyError:
|
97
|
+
return ValueError
|
98
|
+
|
99
|
+
def type_converter_with_retry(type_, val, arg_name):
|
100
|
+
try:
|
101
|
+
if val is None:
|
102
|
+
val = input(
|
103
|
+
f"Enter a value for field '{arg_name}' with type '{arg_type_output[type_]}': "
|
104
|
+
)
|
105
|
+
return arg_types[type_](val)
|
106
|
+
except ValueError:
|
107
|
+
return type_converter_with_retry(type_, None, arg_name)
|
108
|
+
|
109
|
+
arg_types = {"bool": string_to_bool, "u16": float_to_u16, "u64": float_to_u64}
|
110
|
+
arg_type_output = {"bool": "bool", "u16": "float", "u64": "float"}
|
111
|
+
|
112
|
+
call_crafter = {"netuid": netuid}
|
113
|
+
|
114
|
+
for pallet in metadata.pallets:
|
115
|
+
if pallet.name == "AdminUtils":
|
116
|
+
for call in pallet.calls:
|
117
|
+
if call.name == param_name:
|
118
|
+
if "netuid" not in [x.name for x in call.args]:
|
119
|
+
return False, None
|
120
|
+
call_args = [
|
121
|
+
arg for arg in call.args if arg.value["name"] != "netuid"
|
122
|
+
]
|
123
|
+
if len(call_args) == 1:
|
124
|
+
arg = call_args[0].value
|
125
|
+
call_crafter[arg["name"]] = type_converter_with_retry(
|
126
|
+
arg["typeName"], value, arg["name"]
|
127
|
+
)
|
128
|
+
else:
|
129
|
+
for arg_ in call_args:
|
130
|
+
arg = arg_.value
|
131
|
+
call_crafter[arg["name"]] = type_converter_with_retry(
|
132
|
+
arg["typeName"], None, arg["name"]
|
133
|
+
)
|
134
|
+
return True, call_crafter
|
135
|
+
else:
|
136
|
+
return False, None
|
137
|
+
|
138
|
+
|
72
139
|
async def set_hyperparameter_extrinsic(
|
73
140
|
subtensor: "SubtensorInterface",
|
74
141
|
wallet: "Wallet",
|
75
142
|
netuid: int,
|
76
143
|
parameter: str,
|
77
|
-
value: Union[str, bool, float, list[float]],
|
144
|
+
value: Optional[Union[str, bool, float, list[float]]],
|
78
145
|
wait_for_inclusion: bool = False,
|
79
146
|
wait_for_finalization: bool = True,
|
80
147
|
) -> bool:
|
@@ -106,68 +173,90 @@ async def set_hyperparameter_extrinsic(
|
|
106
173
|
)
|
107
174
|
return False
|
108
175
|
|
109
|
-
|
110
|
-
wallet.unlock_coldkey()
|
111
|
-
except KeyFileError:
|
112
|
-
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
176
|
+
if not unlock_key(wallet).success:
|
113
177
|
return False
|
114
178
|
|
115
|
-
|
116
|
-
if extrinsic is None:
|
117
|
-
err_console.print(":cross_mark: [red]Invalid hyperparameter specified.[/red]")
|
118
|
-
return False
|
179
|
+
arbitrary_extrinsic = False
|
119
180
|
|
181
|
+
extrinsic, sudo_ = HYPERPARAMS.get(parameter, ("", False))
|
182
|
+
if not extrinsic:
|
183
|
+
arbitrary_extrinsic, call_params = search_metadata(
|
184
|
+
parameter, value, netuid, subtensor.substrate.metadata
|
185
|
+
)
|
186
|
+
extrinsic = parameter
|
187
|
+
if not arbitrary_extrinsic:
|
188
|
+
err_console.print(
|
189
|
+
":cross_mark: [red]Invalid hyperparameter specified.[/red]"
|
190
|
+
)
|
191
|
+
return False
|
192
|
+
|
193
|
+
substrate = subtensor.substrate
|
194
|
+
msg_value = value if not arbitrary_extrinsic else call_params
|
120
195
|
with console.status(
|
121
|
-
f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}
|
196
|
+
f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}"
|
197
|
+
f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{msg_value}"
|
198
|
+
f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
|
199
|
+
f"{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...",
|
122
200
|
spinner="earth",
|
123
201
|
):
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
)
|
128
|
-
call_params: dict[str, Union[str, bool, float]] = {"netuid": netuid}
|
129
|
-
|
130
|
-
# if input value is a list, iterate through the list and assign values
|
131
|
-
if isinstance(value, list):
|
132
|
-
# Ensure that there are enough values for all non-netuid parameters
|
133
|
-
non_netuid_fields = [
|
134
|
-
param["name"]
|
135
|
-
for param in extrinsic_params["fields"]
|
136
|
-
if "netuid" not in param["name"]
|
137
|
-
]
|
138
|
-
|
139
|
-
if len(value) < len(non_netuid_fields):
|
140
|
-
raise ValueError(
|
141
|
-
"Not enough values provided in the list for all parameters"
|
142
|
-
)
|
143
|
-
|
144
|
-
call_params.update(
|
145
|
-
{str(name): val for name, val in zip(non_netuid_fields, value)}
|
202
|
+
if not arbitrary_extrinsic:
|
203
|
+
extrinsic_params = await substrate.get_metadata_call_function(
|
204
|
+
"AdminUtils", extrinsic
|
146
205
|
)
|
206
|
+
call_params = {"netuid": netuid}
|
207
|
+
|
208
|
+
# if input value is a list, iterate through the list and assign values
|
209
|
+
if isinstance(value, list):
|
210
|
+
# Ensure that there are enough values for all non-netuid parameters
|
211
|
+
non_netuid_fields = [
|
212
|
+
param["name"]
|
213
|
+
for param in extrinsic_params["fields"]
|
214
|
+
if "netuid" not in param["name"]
|
215
|
+
]
|
216
|
+
|
217
|
+
if len(value) < len(non_netuid_fields):
|
218
|
+
raise ValueError(
|
219
|
+
"Not enough values provided in the list for all parameters"
|
220
|
+
)
|
147
221
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
222
|
+
call_params.update(
|
223
|
+
{str(name): val for name, val in zip(non_netuid_fields, value)}
|
224
|
+
)
|
225
|
+
|
226
|
+
else:
|
227
|
+
value_argument = extrinsic_params["fields"][
|
228
|
+
len(extrinsic_params["fields"]) - 1
|
229
|
+
]
|
230
|
+
call_params[str(value_argument["name"])] = value
|
153
231
|
|
154
232
|
# create extrinsic call
|
155
|
-
|
233
|
+
call_ = await substrate.compose_call(
|
156
234
|
call_module="AdminUtils",
|
157
235
|
call_function=extrinsic,
|
158
236
|
call_params=call_params,
|
159
237
|
)
|
238
|
+
if sudo_:
|
239
|
+
call = await substrate.compose_call(
|
240
|
+
call_module="Sudo", call_function="sudo", call_params={"call": call_}
|
241
|
+
)
|
242
|
+
else:
|
243
|
+
call = call_
|
160
244
|
success, err_msg = await subtensor.sign_and_send_extrinsic(
|
161
245
|
call, wallet, wait_for_inclusion, wait_for_finalization
|
162
246
|
)
|
163
247
|
if not success:
|
164
248
|
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
165
249
|
await asyncio.sleep(0.5)
|
166
|
-
|
250
|
+
elif arbitrary_extrinsic:
|
251
|
+
console.print(
|
252
|
+
f":white_heavy_check_mark: "
|
253
|
+
f"[dark_sea_green3]Hyperparameter {parameter} values changed to {call_params}[/dark_sea_green3]"
|
254
|
+
)
|
167
255
|
# Successful registration, final check for membership
|
168
256
|
else:
|
169
257
|
console.print(
|
170
|
-
f":white_heavy_check_mark:
|
258
|
+
f":white_heavy_check_mark: "
|
259
|
+
f"[dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]"
|
171
260
|
)
|
172
261
|
return True
|
173
262
|
|
@@ -266,14 +355,22 @@ def format_call_data(call_data: dict) -> str:
|
|
266
355
|
call_info = call_details[0]
|
267
356
|
call_function, call_args = next(iter(call_info.items()))
|
268
357
|
|
269
|
-
#
|
270
|
-
formatted_args =
|
271
|
-
|
272
|
-
|
273
|
-
|
358
|
+
# Format arguments, handle nested/large payloads
|
359
|
+
formatted_args = []
|
360
|
+
for arg_name, arg_value in call_args.items():
|
361
|
+
if isinstance(arg_value, (tuple, list, dict)):
|
362
|
+
# For large nested, show abbreviated version
|
363
|
+
content_str = str(arg_value)
|
364
|
+
if len(content_str) > 20:
|
365
|
+
formatted_args.append(f"{arg_name}: ... [{len(content_str)}] ...")
|
366
|
+
else:
|
367
|
+
formatted_args.append(f"{arg_name}: {arg_value}")
|
368
|
+
else:
|
369
|
+
formatted_args.append(f"{arg_name}: {arg_value}")
|
274
370
|
|
275
371
|
# Format the final output string
|
276
|
-
|
372
|
+
args_str = ", ".join(formatted_args)
|
373
|
+
return f"{module}.{call_function}({args_str})"
|
277
374
|
|
278
375
|
|
279
376
|
def _validate_proposal_hash(proposal_hash: str) -> bool:
|
@@ -458,28 +555,33 @@ async def sudo_set_hyperparameter(
|
|
458
555
|
subtensor: "SubtensorInterface",
|
459
556
|
netuid: int,
|
460
557
|
param_name: str,
|
461
|
-
param_value: str,
|
558
|
+
param_value: Optional[str],
|
462
559
|
):
|
463
560
|
"""Set subnet hyperparameters."""
|
464
561
|
|
465
562
|
normalized_value: Union[str, bool]
|
466
563
|
if param_name in [
|
467
|
-
"
|
564
|
+
"registration_allowed",
|
468
565
|
"network_pow_registration_allowed",
|
469
566
|
"commit_reveal_weights_enabled",
|
470
567
|
"liquid_alpha_enabled",
|
471
568
|
]:
|
472
569
|
normalized_value = param_value.lower() in ["true", "1"]
|
570
|
+
elif param_value in ("True", "False"):
|
571
|
+
normalized_value = {
|
572
|
+
"True": True,
|
573
|
+
"False": False,
|
574
|
+
}[param_value]
|
473
575
|
else:
|
474
576
|
normalized_value = param_value
|
475
577
|
|
476
578
|
is_allowed_value, value = allowed_value(param_name, normalized_value)
|
477
579
|
if not is_allowed_value:
|
478
580
|
err_console.print(
|
479
|
-
f"Hyperparameter {param_name} value is not within bounds.
|
581
|
+
f"Hyperparameter [dark_orange]{param_name}[/dark_orange] value is not within bounds. "
|
582
|
+
f"Value is {normalized_value} but must be {value}"
|
480
583
|
)
|
481
584
|
return
|
482
|
-
|
483
585
|
success = await set_hyperparameter_extrinsic(
|
484
586
|
subtensor, wallet, netuid, param_name, value
|
485
587
|
)
|
@@ -572,24 +674,30 @@ async def get_senate(subtensor: "SubtensorInterface"):
|
|
572
674
|
return console.print(table)
|
573
675
|
|
574
676
|
|
575
|
-
async def proposals(subtensor: "SubtensorInterface"):
|
677
|
+
async def proposals(subtensor: "SubtensorInterface", verbose: bool):
|
576
678
|
console.print(
|
577
679
|
":satellite: Syncing with chain: [white]{}[/white] ...".format(
|
578
680
|
subtensor.network
|
579
681
|
)
|
580
682
|
)
|
581
|
-
print_verbose("Fetching senate members & proposals")
|
582
683
|
block_hash = await subtensor.substrate.get_chain_head()
|
583
|
-
senate_members, all_proposals = await asyncio.gather(
|
684
|
+
senate_members, all_proposals, current_block = await asyncio.gather(
|
584
685
|
_get_senate_members(subtensor, block_hash),
|
585
686
|
_get_proposals(subtensor, block_hash),
|
687
|
+
subtensor.substrate.get_block_number(block_hash),
|
586
688
|
)
|
587
689
|
|
588
|
-
print_verbose("Fetching member information from Chain")
|
589
690
|
registered_delegate_info: dict[
|
590
691
|
str, DelegatesDetails
|
591
692
|
] = await subtensor.get_delegate_identities()
|
592
693
|
|
694
|
+
title = (
|
695
|
+
f"[bold #4196D6]Bittensor Governance Proposals[/bold #4196D6]\n"
|
696
|
+
f"[steel_blue3]Current Block:[/steel_blue3] {current_block}\t"
|
697
|
+
f"[steel_blue3]Network:[/steel_blue3] {subtensor.network}\n\n"
|
698
|
+
f"[steel_blue3]Active Proposals:[/steel_blue3] {len(all_proposals)}\t"
|
699
|
+
f"[steel_blue3]Senate Size:[/steel_blue3] {len(senate_members)}\n"
|
700
|
+
)
|
593
701
|
table = Table(
|
594
702
|
Column(
|
595
703
|
"[white]HASH",
|
@@ -604,8 +712,8 @@ async def proposals(subtensor: "SubtensorInterface"):
|
|
604
712
|
style="rgb(50,163,219)",
|
605
713
|
),
|
606
714
|
Column("[white]END", style="bright_cyan"),
|
607
|
-
Column("[white]CALLDATA", style="dark_sea_green"),
|
608
|
-
title=
|
715
|
+
Column("[white]CALLDATA", style="dark_sea_green", width=30),
|
716
|
+
title=title,
|
609
717
|
show_footer=True,
|
610
718
|
box=box.SIMPLE_HEAVY,
|
611
719
|
pad_edge=False,
|
@@ -613,16 +721,36 @@ async def proposals(subtensor: "SubtensorInterface"):
|
|
613
721
|
border_style="bright_black",
|
614
722
|
)
|
615
723
|
for hash_, (call_data, vote_data) in all_proposals.items():
|
724
|
+
blocks_remaining = vote_data.end - current_block
|
725
|
+
if blocks_remaining > 0:
|
726
|
+
duration_str = blocks_to_duration(blocks_remaining)
|
727
|
+
vote_end_cell = f"{vote_data.end} [dim](in {duration_str})[/dim]"
|
728
|
+
else:
|
729
|
+
vote_end_cell = f"{vote_data.end} [red](expired)[/red]"
|
730
|
+
|
731
|
+
ayes_threshold = (
|
732
|
+
(len(vote_data.ayes) / vote_data.threshold * 100)
|
733
|
+
if vote_data.threshold > 0
|
734
|
+
else 0
|
735
|
+
)
|
736
|
+
nays_threshold = (
|
737
|
+
(len(vote_data.nays) / vote_data.threshold * 100)
|
738
|
+
if vote_data.threshold > 0
|
739
|
+
else 0
|
740
|
+
)
|
616
741
|
table.add_row(
|
617
|
-
hash_,
|
742
|
+
hash_ if verbose else f"{hash_[:4]}...{hash_[-4:]}",
|
618
743
|
str(vote_data.threshold),
|
619
|
-
|
620
|
-
|
744
|
+
f"{len(vote_data.ayes)} ({ayes_threshold:.2f}%)",
|
745
|
+
f"{len(vote_data.nays)} ({nays_threshold:.2f}%)",
|
621
746
|
display_votes(vote_data, registered_delegate_info),
|
622
|
-
|
747
|
+
vote_end_cell,
|
623
748
|
format_call_data(call_data),
|
624
749
|
)
|
625
|
-
|
750
|
+
console.print(table)
|
751
|
+
console.print(
|
752
|
+
"\n[dim]* Both Ayes and Nays percentages are calculated relative to the proposal's threshold.[/dim]"
|
753
|
+
)
|
626
754
|
|
627
755
|
|
628
756
|
async def senate_vote(
|
@@ -653,10 +781,7 @@ async def senate_vote(
|
|
653
781
|
return False
|
654
782
|
|
655
783
|
# Unlock the wallet.
|
656
|
-
|
657
|
-
wallet.unlock_hotkey()
|
658
|
-
wallet.unlock_coldkey()
|
659
|
-
except KeyFileError:
|
784
|
+
if not unlock_key(wallet, "hot").success and unlock_key(wallet, "cold").success:
|
660
785
|
return False
|
661
786
|
|
662
787
|
console.print(f"Fetching proposals in [dark_orange]network: {subtensor.network}")
|
@@ -732,10 +857,7 @@ async def set_take(
|
|
732
857
|
f"Setting take on [{COLOR_PALETTE['GENERAL']['LINKS']}]network: {subtensor.network}"
|
733
858
|
)
|
734
859
|
|
735
|
-
|
736
|
-
wallet.unlock_hotkey()
|
737
|
-
wallet.unlock_coldkey()
|
738
|
-
except KeyFileError:
|
860
|
+
if not unlock_key(wallet, "hot").success and unlock_key(wallet, "cold").success:
|
739
861
|
return False
|
740
862
|
|
741
863
|
result_ = await _do_set_take()
|