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
@@ -1,4 +1,5 @@
|
|
1
1
|
import ast
|
2
|
+
from collections import namedtuple
|
2
3
|
import math
|
3
4
|
import os
|
4
5
|
import sqlite3
|
@@ -13,7 +14,7 @@ import re
|
|
13
14
|
|
14
15
|
from bittensor_wallet import Wallet, Keypair
|
15
16
|
from bittensor_wallet.utils import SS58_FORMAT
|
16
|
-
from bittensor_wallet.errors import KeyFileError
|
17
|
+
from bittensor_wallet.errors import KeyFileError, PasswordError
|
17
18
|
from bittensor_wallet import utils
|
18
19
|
from jinja2 import Template
|
19
20
|
from markupsafe import Markup
|
@@ -31,12 +32,30 @@ from bittensor_cli.src import defaults, Constants
|
|
31
32
|
|
32
33
|
if TYPE_CHECKING:
|
33
34
|
from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
|
34
|
-
from async_substrate_interface.async_substrate import AsyncSubstrateInterface
|
35
35
|
|
36
36
|
console = Console()
|
37
37
|
err_console = Console(stderr=True)
|
38
38
|
verbose_console = Console(quiet=True)
|
39
39
|
|
40
|
+
UnlockStatus = namedtuple("UnlockStatus", ["success", "message"])
|
41
|
+
|
42
|
+
|
43
|
+
class _Hotkey:
|
44
|
+
def __init__(self, hotkey_ss58=None):
|
45
|
+
self.ss58_address = hotkey_ss58
|
46
|
+
|
47
|
+
|
48
|
+
class WalletLike:
|
49
|
+
def __init__(self, name=None, hotkey_ss58=None, hotkey_str=None):
|
50
|
+
self.name = name
|
51
|
+
self.hotkey_ss58 = hotkey_ss58
|
52
|
+
self.hotkey_str = hotkey_str
|
53
|
+
self._hotkey = _Hotkey(hotkey_ss58)
|
54
|
+
|
55
|
+
@property
|
56
|
+
def hotkey(self):
|
57
|
+
return self._hotkey
|
58
|
+
|
40
59
|
|
41
60
|
def print_console(message: str, colour: str, title: str, console: Console):
|
42
61
|
console.print(
|
@@ -196,13 +215,14 @@ def convert_root_weight_uids_and_vals_to_tensor(
|
|
196
215
|
|
197
216
|
|
198
217
|
def get_hotkey_wallets_for_wallet(
|
199
|
-
wallet: Wallet, show_nulls: bool = False
|
218
|
+
wallet: Wallet, show_nulls: bool = False, show_encrypted: bool = False
|
200
219
|
) -> list[Optional[Wallet]]:
|
201
220
|
"""
|
202
221
|
Returns wallet objects with hotkeys for a single given wallet
|
203
222
|
|
204
223
|
:param wallet: Wallet object to use for the path
|
205
224
|
:param show_nulls: will add `None` into the output if a hotkey is encrypted or not on the device
|
225
|
+
:param show_encrypted: will add some basic info about the encrypted hotkey
|
206
226
|
|
207
227
|
:return: a list of wallets (with Nones included for cases of a hotkey being encrypted or not on the device, if
|
208
228
|
`show_nulls` is set to `True`)
|
@@ -218,12 +238,18 @@ def get_hotkey_wallets_for_wallet(
|
|
218
238
|
hotkey_for_name = Wallet(path=str(wallet_path), name=wallet.name, hotkey=h_name)
|
219
239
|
try:
|
220
240
|
if (
|
221
|
-
hotkey_for_name.hotkey_file.exists_on_device()
|
241
|
+
(exists := hotkey_for_name.hotkey_file.exists_on_device())
|
222
242
|
and not hotkey_for_name.hotkey_file.is_encrypted()
|
223
243
|
# and hotkey_for_name.coldkeypub.ss58_address
|
224
244
|
and hotkey_for_name.hotkey.ss58_address
|
225
245
|
):
|
226
246
|
hotkey_wallets.append(hotkey_for_name)
|
247
|
+
elif (
|
248
|
+
show_encrypted and exists and hotkey_for_name.hotkey_file.is_encrypted()
|
249
|
+
):
|
250
|
+
hotkey_wallets.append(
|
251
|
+
WalletLike(str(wallet_path), "<ENCRYPTED>", h_name)
|
252
|
+
)
|
227
253
|
elif show_nulls:
|
228
254
|
hotkey_wallets.append(None)
|
229
255
|
except (
|
@@ -240,11 +266,14 @@ def get_hotkey_wallets_for_wallet(
|
|
240
266
|
def get_coldkey_wallets_for_path(path: str) -> list[Wallet]:
|
241
267
|
"""Gets all wallets with coldkeys from a given path"""
|
242
268
|
wallet_path = Path(path).expanduser()
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
269
|
+
try:
|
270
|
+
wallets = [
|
271
|
+
Wallet(name=directory.name, path=path)
|
272
|
+
for directory in wallet_path.iterdir()
|
273
|
+
if directory.is_dir()
|
274
|
+
]
|
275
|
+
except FileNotFoundError:
|
276
|
+
wallets = []
|
248
277
|
return wallets
|
249
278
|
|
250
279
|
|
@@ -448,16 +477,13 @@ def get_explorer_url_for_network(
|
|
448
477
|
return explorer_urls
|
449
478
|
|
450
479
|
|
451
|
-
def format_error_message(
|
452
|
-
error_message: Union[dict, Exception], substrate: "AsyncSubstrateInterface"
|
453
|
-
) -> str:
|
480
|
+
def format_error_message(error_message: Union[dict, Exception]) -> str:
|
454
481
|
"""
|
455
482
|
Formats an error message from the Subtensor error information for use in extrinsics.
|
456
483
|
|
457
484
|
Args:
|
458
485
|
error_message: A dictionary containing the error information from Subtensor, or a SubstrateRequestException
|
459
486
|
containing dictionary literal args.
|
460
|
-
substrate: The initialised SubstrateInterface object to use.
|
461
487
|
|
462
488
|
Returns:
|
463
489
|
str: A formatted error message string.
|
@@ -479,7 +505,7 @@ def format_error_message(
|
|
479
505
|
elif all(x in d for x in ["code", "message", "data"]):
|
480
506
|
new_error_message = d
|
481
507
|
break
|
482
|
-
except
|
508
|
+
except ValueError:
|
483
509
|
pass
|
484
510
|
if new_error_message is None:
|
485
511
|
return_val = " ".join(error_message.args)
|
@@ -500,7 +526,10 @@ def format_error_message(
|
|
500
526
|
|
501
527
|
# subtensor custom error marker
|
502
528
|
if err_data.startswith("Custom error:"):
|
503
|
-
err_description =
|
529
|
+
err_description = (
|
530
|
+
f"{err_data} | Please consult "
|
531
|
+
f"https://docs.bittensor.com/subtensor-nodes/subtensor-error-messages"
|
532
|
+
)
|
504
533
|
else:
|
505
534
|
err_description = err_data
|
506
535
|
|
@@ -535,7 +564,8 @@ def decode_hex_identity_dict(info_dictionary) -> dict[str, Any]:
|
|
535
564
|
"""
|
536
565
|
Decodes hex-encoded strings in a dictionary.
|
537
566
|
|
538
|
-
This function traverses the given dictionary, identifies hex-encoded strings, and decodes them into readable
|
567
|
+
This function traverses the given dictionary, identifies hex-encoded strings, and decodes them into readable
|
568
|
+
strings. It handles nested dictionaries and lists within the dictionary.
|
539
569
|
|
540
570
|
Args:
|
541
571
|
info_dictionary (dict): The dictionary containing hex-encoded strings to decode.
|
@@ -557,7 +587,7 @@ def decode_hex_identity_dict(info_dictionary) -> dict[str, Any]:
|
|
557
587
|
def get_decoded(data: str) -> str:
|
558
588
|
"""Decodes a hex-encoded string."""
|
559
589
|
try:
|
560
|
-
return
|
590
|
+
return hex_to_bytes(data).decode()
|
561
591
|
except UnicodeDecodeError:
|
562
592
|
print(f"Could not decode: {key}: {item}")
|
563
593
|
|
@@ -1096,6 +1126,7 @@ def prompt_for_identity(
|
|
1096
1126
|
|
1097
1127
|
|
1098
1128
|
def prompt_for_subnet_identity(
|
1129
|
+
current_identity: dict,
|
1099
1130
|
subnet_name: Optional[str],
|
1100
1131
|
github_repo: Optional[str],
|
1101
1132
|
subnet_contact: Optional[str],
|
@@ -1180,7 +1211,7 @@ def prompt_for_subnet_identity(
|
|
1180
1211
|
prompt,
|
1181
1212
|
rejection=rejection_func,
|
1182
1213
|
rejection_text=rejection_msg,
|
1183
|
-
default=
|
1214
|
+
default=current_identity.get(key, ""),
|
1184
1215
|
show_default=True,
|
1185
1216
|
)
|
1186
1217
|
|
@@ -1262,11 +1293,14 @@ def is_linux():
|
|
1262
1293
|
"""Returns True if the operating system is Linux."""
|
1263
1294
|
return platform.system().lower() == "linux"
|
1264
1295
|
|
1296
|
+
|
1265
1297
|
def validate_rate_tolerance(value: Optional[float]) -> Optional[float]:
|
1266
1298
|
"""Validates rate tolerance input"""
|
1267
1299
|
if value is not None:
|
1268
1300
|
if value < 0:
|
1269
|
-
raise typer.BadParameter(
|
1301
|
+
raise typer.BadParameter(
|
1302
|
+
"Rate tolerance cannot be negative (less than 0%)."
|
1303
|
+
)
|
1270
1304
|
if value > 1:
|
1271
1305
|
raise typer.BadParameter("Rate tolerance cannot be greater than 1 (100%).")
|
1272
1306
|
if value > 0.5:
|
@@ -1275,3 +1309,79 @@ def validate_rate_tolerance(value: Optional[float]) -> Optional[float]:
|
|
1275
1309
|
"This may result in unfavorable transaction execution.[/yellow]"
|
1276
1310
|
)
|
1277
1311
|
return value
|
1312
|
+
|
1313
|
+
|
1314
|
+
def unlock_key(
|
1315
|
+
wallet: Wallet, unlock_type="cold", print_out: bool = True
|
1316
|
+
) -> "UnlockStatus":
|
1317
|
+
"""
|
1318
|
+
Attempts to decrypt a wallet's coldkey or hotkey
|
1319
|
+
Args:
|
1320
|
+
wallet: a Wallet object
|
1321
|
+
unlock_type: the key type, 'cold' or 'hot'
|
1322
|
+
print_out: whether to print out the error message to the err_console
|
1323
|
+
|
1324
|
+
Returns: UnlockStatus for success status of unlock, with error message if unsuccessful
|
1325
|
+
|
1326
|
+
"""
|
1327
|
+
if unlock_type == "cold":
|
1328
|
+
unlocker = "unlock_coldkey"
|
1329
|
+
elif unlock_type == "hot":
|
1330
|
+
unlocker = "unlock_hotkey"
|
1331
|
+
else:
|
1332
|
+
raise ValueError(
|
1333
|
+
f"Invalid unlock type provided: {unlock_type}. Must be 'cold' or 'hot'."
|
1334
|
+
)
|
1335
|
+
try:
|
1336
|
+
getattr(wallet, unlocker)()
|
1337
|
+
return UnlockStatus(True, "")
|
1338
|
+
except PasswordError:
|
1339
|
+
err_msg = f"The password used to decrypt your {unlock_type.capitalize()}key Keyfile is invalid."
|
1340
|
+
if print_out:
|
1341
|
+
err_console.print(f":cross_mark: [red]{err_msg}[/red]")
|
1342
|
+
return UnlockStatus(False, err_msg)
|
1343
|
+
except KeyFileError:
|
1344
|
+
err_msg = f"{unlock_type.capitalize()}key Keyfile is corrupt, non-writable, or non-readable, or non-existent."
|
1345
|
+
if print_out:
|
1346
|
+
err_console.print(f":cross_mark: [red]{err_msg}[/red]")
|
1347
|
+
return UnlockStatus(False, err_msg)
|
1348
|
+
|
1349
|
+
|
1350
|
+
def hex_to_bytes(hex_str: str) -> bytes:
|
1351
|
+
"""
|
1352
|
+
Converts a hex-encoded string into bytes. Handles 0x-prefixed and non-prefixed hex-encoded strings.
|
1353
|
+
"""
|
1354
|
+
if hex_str.startswith("0x"):
|
1355
|
+
bytes_result = bytes.fromhex(hex_str[2:])
|
1356
|
+
else:
|
1357
|
+
bytes_result = bytes.fromhex(hex_str)
|
1358
|
+
return bytes_result
|
1359
|
+
|
1360
|
+
|
1361
|
+
def blocks_to_duration(blocks: int) -> str:
|
1362
|
+
"""Convert blocks to human readable duration string using two largest units.
|
1363
|
+
|
1364
|
+
Args:
|
1365
|
+
blocks (int): Number of blocks (12s per block)
|
1366
|
+
|
1367
|
+
Returns:
|
1368
|
+
str: Duration string like '2d 5h', '3h 45m', '2m 10s', or '0s'
|
1369
|
+
"""
|
1370
|
+
if blocks <= 0:
|
1371
|
+
return "0s"
|
1372
|
+
|
1373
|
+
seconds = blocks * 12
|
1374
|
+
intervals = [
|
1375
|
+
("d", 86400), # 60 * 60 * 24
|
1376
|
+
("h", 3600), # 60 * 60
|
1377
|
+
("m", 60),
|
1378
|
+
("s", 1),
|
1379
|
+
]
|
1380
|
+
results = []
|
1381
|
+
for unit, seconds_per_unit in intervals:
|
1382
|
+
unit_count = seconds // seconds_per_unit
|
1383
|
+
seconds %= seconds_per_unit
|
1384
|
+
if unit_count > 0:
|
1385
|
+
results.append(f"{unit_count}{unit}")
|
1386
|
+
# Return only the first two non-zero units
|
1387
|
+
return " ".join(results[:2]) or "0s"
|
@@ -107,15 +107,13 @@ async def stake_add(
|
|
107
107
|
)
|
108
108
|
return
|
109
109
|
else:
|
110
|
-
err_out(
|
111
|
-
f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}"
|
112
|
-
)
|
110
|
+
err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
|
113
111
|
return
|
114
112
|
else:
|
115
113
|
await response.process_events()
|
116
114
|
if not await response.is_success:
|
117
115
|
err_out(
|
118
|
-
f"\n{failure_prelude} with error: {format_error_message(await response.error_message
|
116
|
+
f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
|
119
117
|
)
|
120
118
|
else:
|
121
119
|
block_hash = await subtensor.substrate.get_chain_head()
|
@@ -180,15 +178,13 @@ async def stake_add(
|
|
180
178
|
extrinsic, wait_for_inclusion=True, wait_for_finalization=False
|
181
179
|
)
|
182
180
|
except SubstrateRequestException as e:
|
183
|
-
err_out(
|
184
|
-
f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}"
|
185
|
-
)
|
181
|
+
err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
|
186
182
|
return
|
187
183
|
else:
|
188
184
|
await response.process_events()
|
189
185
|
if not await response.is_success:
|
190
186
|
err_out(
|
191
|
-
f"\n{failure_prelude} with error: {format_error_message(await response.error_message
|
187
|
+
f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
|
192
188
|
)
|
193
189
|
else:
|
194
190
|
new_balance, new_stake = await asyncio.gather(
|
@@ -2,7 +2,6 @@ import asyncio
|
|
2
2
|
from typing import Optional
|
3
3
|
|
4
4
|
from bittensor_wallet import Wallet
|
5
|
-
from bittensor_wallet.errors import KeyFileError
|
6
5
|
from rich.prompt import Confirm, Prompt, IntPrompt
|
7
6
|
from rich.table import Table
|
8
7
|
from rich.text import Text
|
@@ -19,6 +18,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
19
18
|
u64_to_float,
|
20
19
|
is_valid_ss58_address,
|
21
20
|
format_error_message,
|
21
|
+
unlock_key,
|
22
22
|
)
|
23
23
|
|
24
24
|
|
@@ -99,10 +99,8 @@ async def set_children_extrinsic(
|
|
99
99
|
return False, "Operation Cancelled"
|
100
100
|
|
101
101
|
# Decrypt coldkey.
|
102
|
-
|
103
|
-
|
104
|
-
except KeyFileError:
|
105
|
-
return False, "There was an error unlocking your coldkey."
|
102
|
+
if not (unlock_status := unlock_key(wallet, print_out=False)).success:
|
103
|
+
return False, unlock_status.message
|
106
104
|
|
107
105
|
with console.status(
|
108
106
|
f":satellite: {operation} on [white]{subtensor.network}[/white] ..."
|
@@ -185,10 +183,8 @@ async def set_childkey_take_extrinsic(
|
|
185
183
|
return False, "Operation Cancelled"
|
186
184
|
|
187
185
|
# Decrypt coldkey.
|
188
|
-
|
189
|
-
|
190
|
-
except KeyFileError:
|
191
|
-
return False, "There was an error unlocking your coldkey."
|
186
|
+
if not (unlock_status := unlock_key(wallet, print_out=False)).success:
|
187
|
+
return False, unlock_status.message
|
192
188
|
|
193
189
|
with console.status(
|
194
190
|
f":satellite: Setting childkey take on [white]{subtensor.network}[/white] ..."
|
@@ -239,7 +235,7 @@ async def set_childkey_take_extrinsic(
|
|
239
235
|
except SubstrateRequestException as e:
|
240
236
|
return (
|
241
237
|
False,
|
242
|
-
f"Exception occurred while setting childkey take: {format_error_message(e
|
238
|
+
f"Exception occurred while setting childkey take: {format_error_message(e)}",
|
243
239
|
)
|
244
240
|
|
245
241
|
|
@@ -260,12 +256,10 @@ async def get_childkey_take(subtensor, hotkey: str, netuid: int) -> Optional[int
|
|
260
256
|
params=[hotkey, netuid],
|
261
257
|
)
|
262
258
|
if childkey_take_:
|
263
|
-
return int(childkey_take_
|
259
|
+
return int(childkey_take_)
|
264
260
|
|
265
261
|
except SubstrateRequestException as e:
|
266
|
-
err_console.print(
|
267
|
-
f"Error querying ChildKeys: {format_error_message(e, subtensor.substrate)}"
|
268
|
-
)
|
262
|
+
err_console.print(f"Error querying ChildKeys: {format_error_message(e)}")
|
269
263
|
return None
|
270
264
|
|
271
265
|
|
@@ -502,7 +496,7 @@ async def set_children(
|
|
502
496
|
subtensor: "SubtensorInterface",
|
503
497
|
children: list[str],
|
504
498
|
proportions: list[float],
|
505
|
-
netuid: Optional[int],
|
499
|
+
netuid: Optional[int] = None,
|
506
500
|
wait_for_inclusion: bool = True,
|
507
501
|
wait_for_finalization: bool = True,
|
508
502
|
prompt: bool = True,
|
@@ -589,8 +583,8 @@ async def revoke_children(
|
|
589
583
|
netuid: Optional[int] = None,
|
590
584
|
wait_for_inclusion: bool = True,
|
591
585
|
wait_for_finalization: bool = True,
|
586
|
+
prompt: bool = True,
|
592
587
|
):
|
593
|
-
# TODO seek clarification on use of asking hotkey vs how we do it now
|
594
588
|
"""
|
595
589
|
Revokes the children hotkeys associated with a given network identifier (netuid).
|
596
590
|
"""
|
@@ -601,7 +595,7 @@ async def revoke_children(
|
|
601
595
|
netuid=netuid,
|
602
596
|
hotkey=wallet.hotkey.ss58_address,
|
603
597
|
children_with_proportions=[],
|
604
|
-
prompt=
|
598
|
+
prompt=prompt,
|
605
599
|
wait_for_inclusion=wait_for_inclusion,
|
606
600
|
wait_for_finalization=wait_for_finalization,
|
607
601
|
)
|
@@ -630,7 +624,7 @@ async def revoke_children(
|
|
630
624
|
netuid=netuid,
|
631
625
|
hotkey=wallet.hotkey.ss58_address,
|
632
626
|
children_with_proportions=[],
|
633
|
-
prompt=
|
627
|
+
prompt=prompt,
|
634
628
|
wait_for_inclusion=True,
|
635
629
|
wait_for_finalization=False,
|
636
630
|
)
|
@@ -790,7 +784,7 @@ async def childkey_take(
|
|
790
784
|
netuid=netuid,
|
791
785
|
hotkey=wallet.hotkey.ss58_address,
|
792
786
|
take=take,
|
793
|
-
prompt=
|
787
|
+
prompt=prompt,
|
794
788
|
wait_for_inclusion=True,
|
795
789
|
wait_for_finalization=False,
|
796
790
|
)
|