bittensor-cli 8.2.0__py3-none-any.whl → 8.3.1__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 +1 -1
- bittensor_cli/cli.py +186 -123
- bittensor_cli/src/__init__.py +4 -2
- bittensor_cli/src/bittensor/async_substrate_interface.py +35 -25
- bittensor_cli/src/bittensor/extrinsics/registration.py +38 -30
- bittensor_cli/src/bittensor/extrinsics/root.py +3 -7
- bittensor_cli/src/bittensor/extrinsics/transfer.py +17 -6
- bittensor_cli/src/bittensor/subtensor_interface.py +11 -11
- bittensor_cli/src/bittensor/utils.py +50 -8
- bittensor_cli/src/commands/root.py +6 -19
- bittensor_cli/src/commands/stake/children_hotkeys.py +12 -14
- bittensor_cli/src/commands/stake/stake.py +8 -14
- bittensor_cli/src/commands/subnets.py +4 -5
- bittensor_cli/src/commands/sudo.py +2 -4
- bittensor_cli/src/commands/wallets.py +68 -73
- bittensor_cli/src/commands/weights.py +4 -2
- {bittensor_cli-8.2.0.dist-info → bittensor_cli-8.3.1.dist-info}/METADATA +3 -2
- bittensor_cli-8.3.1.dist-info/RECORD +31 -0
- {bittensor_cli-8.2.0.dist-info → bittensor_cli-8.3.1.dist-info}/WHEEL +1 -1
- bittensor_cli-8.2.0.dist-info/RECORD +0 -31
- {bittensor_cli-8.2.0.dist-info → bittensor_cli-8.3.1.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.2.0.dist-info → bittensor_cli-8.3.1.dist-info}/top_level.txt +0 -0
bittensor_cli/src/__init__.py
CHANGED
@@ -4,16 +4,18 @@ from typing import Any, Optional
|
|
4
4
|
|
5
5
|
|
6
6
|
class Constants:
|
7
|
-
networks = ["local", "finney", "test", "archive"]
|
7
|
+
networks = ["local", "finney", "test", "archive", "subvortex"]
|
8
8
|
finney_entrypoint = "wss://entrypoint-finney.opentensor.ai:443"
|
9
9
|
finney_test_entrypoint = "wss://test.finney.opentensor.ai:443"
|
10
10
|
archive_entrypoint = "wss://archive.chain.opentensor.ai:443"
|
11
|
-
|
11
|
+
subvortex_entrypoint = "ws://subvortex.info:9944"
|
12
|
+
local_entrypoint = "ws://127.0.0.1:9944"
|
12
13
|
network_map = {
|
13
14
|
"finney": finney_entrypoint,
|
14
15
|
"test": finney_test_entrypoint,
|
15
16
|
"archive": archive_entrypoint,
|
16
17
|
"local": local_entrypoint,
|
18
|
+
"subvortex": subvortex_entrypoint,
|
17
19
|
}
|
18
20
|
delegates_detail_url = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json"
|
19
21
|
|
@@ -6,13 +6,14 @@ from dataclasses import dataclass
|
|
6
6
|
from hashlib import blake2b
|
7
7
|
from typing import Optional, Any, Union, Callable, Awaitable, cast
|
8
8
|
|
9
|
-
from bt_decode import PortableRegistry, decode as decode_by_type_string, MetadataV15
|
10
9
|
from async_property import async_property
|
10
|
+
from bt_decode import PortableRegistry, decode as decode_by_type_string, MetadataV15
|
11
|
+
from bittensor_wallet import Keypair
|
12
|
+
from packaging import version
|
11
13
|
from scalecodec import GenericExtrinsic
|
12
14
|
from scalecodec.base import ScaleBytes, ScaleType, RuntimeConfigurationObject
|
13
15
|
from scalecodec.type_registry import load_type_registry_preset
|
14
16
|
from scalecodec.types import GenericCall
|
15
|
-
from bittensor_wallet import Keypair
|
16
17
|
from substrateinterface.exceptions import (
|
17
18
|
SubstrateRequestException,
|
18
19
|
ExtrinsicNotFound,
|
@@ -460,6 +461,9 @@ class Runtime:
|
|
460
461
|
self.runtime_config = runtime_config
|
461
462
|
self.metadata = metadata
|
462
463
|
|
464
|
+
def __str__(self):
|
465
|
+
return f"Runtime: {self.chain} | {self.config}"
|
466
|
+
|
463
467
|
@property
|
464
468
|
def implements_scaleinfo(self) -> bool:
|
465
469
|
"""
|
@@ -682,9 +686,9 @@ class Websocket:
|
|
682
686
|
try:
|
683
687
|
self._receiving_task.cancel()
|
684
688
|
await self._receiving_task
|
689
|
+
await self.ws.close()
|
685
690
|
except (AttributeError, asyncio.CancelledError):
|
686
691
|
pass
|
687
|
-
await self.ws.close()
|
688
692
|
self.ws = None
|
689
693
|
self._initialized = False
|
690
694
|
self._receiving_task = None
|
@@ -768,14 +772,13 @@ class AsyncSubstrateInterface:
|
|
768
772
|
"""
|
769
773
|
self.chain_endpoint = chain_endpoint
|
770
774
|
self.__chain = chain_name
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
)
|
775
|
+
options = {
|
776
|
+
"max_size": 2**32,
|
777
|
+
"write_limit": 2**16,
|
778
|
+
}
|
779
|
+
if version.parse(websockets.__version__) < version.parse("14.0"):
|
780
|
+
options.update({"read_limit": 2**16})
|
781
|
+
self.ws = Websocket(chain_endpoint, options=options)
|
779
782
|
self._lock = asyncio.Lock()
|
780
783
|
self.last_block_hash: Optional[str] = None
|
781
784
|
self.config = {
|
@@ -897,9 +900,10 @@ class AsyncSubstrateInterface:
|
|
897
900
|
|
898
901
|
async def get_runtime(block_hash, block_id) -> Runtime:
|
899
902
|
# Check if runtime state already set to current block
|
900
|
-
if (
|
901
|
-
|
902
|
-
|
903
|
+
if (
|
904
|
+
(block_hash and block_hash == self.last_block_hash)
|
905
|
+
or (block_id and block_id == self.block_id)
|
906
|
+
) and self.metadata is not None:
|
903
907
|
return Runtime(
|
904
908
|
self.chain,
|
905
909
|
self.runtime_config,
|
@@ -945,9 +949,11 @@ class AsyncSubstrateInterface:
|
|
945
949
|
raise SubstrateRequestException(
|
946
950
|
f"No runtime information for block '{block_hash}'"
|
947
951
|
)
|
948
|
-
|
949
952
|
# Check if runtime state already set to current block
|
950
|
-
if
|
953
|
+
if (
|
954
|
+
runtime_info.get("specVersion") == self.runtime_version
|
955
|
+
and self.metadata is not None
|
956
|
+
):
|
951
957
|
return Runtime(
|
952
958
|
self.chain,
|
953
959
|
self.runtime_config,
|
@@ -962,16 +968,19 @@ class AsyncSubstrateInterface:
|
|
962
968
|
if self.runtime_version in self.__metadata_cache:
|
963
969
|
# Get metadata from cache
|
964
970
|
# self.debug_message('Retrieved metadata for {} from memory'.format(self.runtime_version))
|
965
|
-
self.metadata = self.__metadata_cache[
|
971
|
+
metadata = self.metadata = self.__metadata_cache[
|
972
|
+
self.runtime_version
|
973
|
+
]
|
966
974
|
else:
|
967
|
-
self.metadata = await self.get_block_metadata(
|
975
|
+
metadata = self.metadata = await self.get_block_metadata(
|
968
976
|
block_hash=runtime_block_hash, decode=True
|
969
977
|
)
|
970
978
|
# self.debug_message('Retrieved metadata for {} from Substrate node'.format(self.runtime_version))
|
971
979
|
|
972
980
|
# Update metadata cache
|
973
981
|
self.__metadata_cache[self.runtime_version] = self.metadata
|
974
|
-
|
982
|
+
else:
|
983
|
+
metadata = self.metadata
|
975
984
|
# Update type registry
|
976
985
|
self.reload_type_registry(use_remote_preset=False, auto_discover=True)
|
977
986
|
|
@@ -1012,7 +1021,10 @@ class AsyncSubstrateInterface:
|
|
1012
1021
|
if block_id and block_hash:
|
1013
1022
|
raise ValueError("Cannot provide block_hash and block_id at the same time")
|
1014
1023
|
|
1015
|
-
if
|
1024
|
+
if (
|
1025
|
+
not (runtime := self.runtime_cache.retrieve(block_id, block_hash))
|
1026
|
+
or runtime.metadata is None
|
1027
|
+
):
|
1016
1028
|
runtime = await get_runtime(block_hash, block_id)
|
1017
1029
|
self.runtime_cache.add_item(block_id, block_hash, runtime)
|
1018
1030
|
return runtime
|
@@ -1123,7 +1135,7 @@ class AsyncSubstrateInterface:
|
|
1123
1135
|
-------
|
1124
1136
|
StorageKey
|
1125
1137
|
"""
|
1126
|
-
await self.init_runtime(block_hash=block_hash)
|
1138
|
+
runtime = await self.init_runtime(block_hash=block_hash)
|
1127
1139
|
|
1128
1140
|
return StorageKey.create_from_storage_function(
|
1129
1141
|
pallet,
|
@@ -1707,9 +1719,7 @@ class AsyncSubstrateInterface:
|
|
1707
1719
|
)
|
1708
1720
|
result = await self._make_rpc_request(payloads, runtime=runtime)
|
1709
1721
|
if "error" in result[payload_id][0]:
|
1710
|
-
raise SubstrateRequestException(
|
1711
|
-
result[payload_id][0]["error"]["message"]
|
1712
|
-
)
|
1722
|
+
raise SubstrateRequestException(result[payload_id][0]["error"]["message"])
|
1713
1723
|
if "result" in result[payload_id][0]:
|
1714
1724
|
return result[payload_id][0]
|
1715
1725
|
else:
|
@@ -2274,7 +2284,7 @@ class AsyncSubstrateInterface:
|
|
2274
2284
|
MetadataModuleConstants
|
2275
2285
|
"""
|
2276
2286
|
|
2277
|
-
|
2287
|
+
await self.init_runtime(block_hash=block_hash)
|
2278
2288
|
|
2279
2289
|
for module in self.metadata.pallets:
|
2280
2290
|
if module_name == module.name and module.constants:
|
@@ -16,10 +16,10 @@ import random
|
|
16
16
|
import time
|
17
17
|
import typing
|
18
18
|
from typing import Optional
|
19
|
+
import subprocess
|
19
20
|
|
20
21
|
import backoff
|
21
22
|
from bittensor_wallet import Wallet
|
22
|
-
from bittensor_wallet.errors import KeyFileError
|
23
23
|
from Crypto.Hash import keccak
|
24
24
|
import numpy as np
|
25
25
|
from rich.prompt import Confirm
|
@@ -35,6 +35,8 @@ from bittensor_cli.src.bittensor.utils import (
|
|
35
35
|
millify,
|
36
36
|
get_human_readable,
|
37
37
|
print_verbose,
|
38
|
+
print_error,
|
39
|
+
unlock_key,
|
38
40
|
)
|
39
41
|
|
40
42
|
if typing.TYPE_CHECKING:
|
@@ -512,12 +514,13 @@ async def register_extrinsic(
|
|
512
514
|
with console.status(
|
513
515
|
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...",
|
514
516
|
spinner="aesthetic",
|
515
|
-
):
|
517
|
+
) as status:
|
516
518
|
neuron = await get_neuron_for_pubkey_and_subnet()
|
517
519
|
if not neuron.is_null:
|
518
|
-
|
519
|
-
|
520
|
-
|
520
|
+
print_error(
|
521
|
+
f"Wallet {wallet} is already registered on subnet {neuron.netuid} with uid {neuron.uid}",
|
522
|
+
status,
|
523
|
+
)
|
521
524
|
return True
|
522
525
|
|
523
526
|
if prompt:
|
@@ -611,7 +614,8 @@ async def register_extrinsic(
|
|
611
614
|
success, err_msg = True, ""
|
612
615
|
else:
|
613
616
|
await response.process_events()
|
614
|
-
|
617
|
+
success = await response.is_success
|
618
|
+
if not success:
|
615
619
|
success, err_msg = (
|
616
620
|
False,
|
617
621
|
format_error_message(
|
@@ -619,23 +623,23 @@ async def register_extrinsic(
|
|
619
623
|
substrate=subtensor.substrate,
|
620
624
|
),
|
621
625
|
)
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
626
|
+
# Look error here
|
627
|
+
# https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs
|
628
|
+
|
629
|
+
if "HotKeyAlreadyRegisteredInSubNet" in err_msg:
|
630
|
+
console.print(
|
631
|
+
f":white_heavy_check_mark: [green]Already Registered on "
|
632
|
+
f"[bold]subnet:{netuid}[/bold][/green]"
|
633
|
+
)
|
634
|
+
return True
|
635
|
+
err_console.print(
|
636
|
+
f":cross_mark: [red]Failed[/red]: {err_msg}"
|
630
637
|
)
|
631
|
-
|
632
|
-
|
633
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
634
|
-
await asyncio.sleep(0.5)
|
638
|
+
await asyncio.sleep(0.5)
|
635
639
|
|
636
640
|
# Successful registration, final check for neuron and pubkey
|
637
|
-
|
638
|
-
console.print(":satellite: Checking
|
641
|
+
if success:
|
642
|
+
console.print(":satellite: Checking Registration status...")
|
639
643
|
is_registered = await is_hotkey_registered(
|
640
644
|
subtensor,
|
641
645
|
netuid=netuid,
|
@@ -722,10 +726,8 @@ async def run_faucet_extrinsic(
|
|
722
726
|
return False, "Requires torch"
|
723
727
|
|
724
728
|
# Unlock coldkey
|
725
|
-
|
726
|
-
|
727
|
-
except KeyFileError:
|
728
|
-
return False, "There was an error unlocking your coldkey"
|
729
|
+
if not (unlock_status := unlock_key(wallet, print_out=False)).success:
|
730
|
+
return False, unlock_status.message
|
729
731
|
|
730
732
|
# Get previous balance.
|
731
733
|
old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
|
@@ -914,6 +916,9 @@ async def _block_solver(
|
|
914
916
|
stop_event.clear()
|
915
917
|
|
916
918
|
solution_queue = Queue()
|
919
|
+
if cuda:
|
920
|
+
num_processes = len(dev_id)
|
921
|
+
|
917
922
|
finished_queues = [Queue() for _ in range(num_processes)]
|
918
923
|
check_block = Lock()
|
919
924
|
|
@@ -923,7 +928,6 @@ async def _block_solver(
|
|
923
928
|
|
924
929
|
if cuda:
|
925
930
|
## Create a worker per CUDA device
|
926
|
-
num_processes = len(dev_id)
|
927
931
|
solvers = [
|
928
932
|
_CUDASolver(
|
929
933
|
i,
|
@@ -1231,8 +1235,14 @@ def _terminate_workers_and_wait_for_exit(
|
|
1231
1235
|
if isinstance(worker, Queue_Type):
|
1232
1236
|
worker.join_thread()
|
1233
1237
|
else:
|
1234
|
-
|
1235
|
-
|
1238
|
+
try:
|
1239
|
+
worker.join(3.0)
|
1240
|
+
except subprocess.TimeoutExpired:
|
1241
|
+
worker.terminate()
|
1242
|
+
try:
|
1243
|
+
worker.close()
|
1244
|
+
except ValueError:
|
1245
|
+
worker.terminate()
|
1236
1246
|
|
1237
1247
|
|
1238
1248
|
# TODO verify this works with async
|
@@ -1627,9 +1637,7 @@ async def swap_hotkey_extrinsic(
|
|
1627
1637
|
)
|
1628
1638
|
return False
|
1629
1639
|
|
1630
|
-
|
1631
|
-
wallet.unlock_coldkey()
|
1632
|
-
except KeyFileError:
|
1640
|
+
if not unlock_key(wallet).success:
|
1633
1641
|
return False
|
1634
1642
|
|
1635
1643
|
if prompt:
|
@@ -21,7 +21,6 @@ import time
|
|
21
21
|
from typing import Union, List, TYPE_CHECKING
|
22
22
|
|
23
23
|
from bittensor_wallet import Wallet, Keypair
|
24
|
-
from bittensor_wallet.errors import KeyFileError
|
25
24
|
import numpy as np
|
26
25
|
from numpy.typing import NDArray
|
27
26
|
from rich.prompt import Confirm
|
@@ -37,6 +36,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
37
36
|
u16_normalized_float,
|
38
37
|
print_verbose,
|
39
38
|
format_error_message,
|
39
|
+
unlock_key,
|
40
40
|
)
|
41
41
|
|
42
42
|
if TYPE_CHECKING:
|
@@ -306,9 +306,7 @@ async def root_register_extrinsic(
|
|
306
306
|
the response is `True`.
|
307
307
|
"""
|
308
308
|
|
309
|
-
|
310
|
-
wallet.unlock_coldkey()
|
311
|
-
except KeyFileError:
|
309
|
+
if not unlock_key(wallet).success:
|
312
310
|
return False
|
313
311
|
|
314
312
|
print_verbose(f"Checking if hotkey ({wallet.hotkey_str}) is registered on root")
|
@@ -426,9 +424,7 @@ async def set_root_weights_extrinsic(
|
|
426
424
|
err_console.print("Your hotkey is not registered to the root network")
|
427
425
|
return False
|
428
426
|
|
429
|
-
|
430
|
-
wallet.unlock_coldkey()
|
431
|
-
except KeyFileError:
|
427
|
+
if not unlock_key(wallet).success:
|
432
428
|
return False
|
433
429
|
|
434
430
|
# First convert types.
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
|
3
3
|
from bittensor_wallet import Wallet
|
4
|
-
from bittensor_wallet.errors import KeyFileError
|
5
4
|
from rich.prompt import Confirm
|
6
5
|
from substrateinterface.exceptions import SubstrateRequestException
|
7
6
|
|
@@ -15,6 +14,8 @@ from bittensor_cli.src.bittensor.utils import (
|
|
15
14
|
format_error_message,
|
16
15
|
get_explorer_url_for_network,
|
17
16
|
is_valid_bittensor_address_or_public_key,
|
17
|
+
print_error,
|
18
|
+
unlock_key,
|
18
19
|
)
|
19
20
|
|
20
21
|
|
@@ -23,6 +24,7 @@ async def transfer_extrinsic(
|
|
23
24
|
wallet: Wallet,
|
24
25
|
destination: str,
|
25
26
|
amount: Balance,
|
27
|
+
transfer_all: bool = False,
|
26
28
|
wait_for_inclusion: bool = True,
|
27
29
|
wait_for_finalization: bool = False,
|
28
30
|
keep_alive: bool = True,
|
@@ -34,6 +36,7 @@ async def transfer_extrinsic(
|
|
34
36
|
:param wallet: Bittensor wallet object to make transfer from.
|
35
37
|
:param destination: Destination public key address (ss58_address or ed25519) of recipient.
|
36
38
|
:param amount: Amount to stake as Bittensor balance.
|
39
|
+
:param transfer_all: Whether to transfer all funds from this wallet to the destination address.
|
37
40
|
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`,
|
38
41
|
or returns `False` if the extrinsic fails to enter the block within the timeout.
|
39
42
|
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning
|
@@ -98,7 +101,11 @@ async def transfer_extrinsic(
|
|
98
101
|
block_hash_ = response.block_hash
|
99
102
|
return True, block_hash_, ""
|
100
103
|
else:
|
101
|
-
return
|
104
|
+
return (
|
105
|
+
False,
|
106
|
+
"",
|
107
|
+
format_error_message(await response.error_message, subtensor.substrate),
|
108
|
+
)
|
102
109
|
|
103
110
|
# Validate destination address.
|
104
111
|
if not is_valid_bittensor_address_or_public_key(destination):
|
@@ -108,9 +115,7 @@ async def transfer_extrinsic(
|
|
108
115
|
return False
|
109
116
|
console.print(f"[dark_orange]Initiating transfer on network: {subtensor.network}")
|
110
117
|
# Unlock wallet coldkey.
|
111
|
-
|
112
|
-
wallet.unlock_coldkey()
|
113
|
-
except KeyFileError:
|
118
|
+
if not unlock_key(wallet).success:
|
114
119
|
return False
|
115
120
|
|
116
121
|
# Check balance.
|
@@ -135,6 +140,12 @@ async def transfer_extrinsic(
|
|
135
140
|
existential_deposit = Balance(0)
|
136
141
|
|
137
142
|
# Check if we have enough balance.
|
143
|
+
if transfer_all is True:
|
144
|
+
amount = account_balance - fee - existential_deposit
|
145
|
+
if amount < Balance(0):
|
146
|
+
print_error("Not enough balance to transfer")
|
147
|
+
return False
|
148
|
+
|
138
149
|
if account_balance < (amount + fee + existential_deposit):
|
139
150
|
err_console.print(
|
140
151
|
":cross_mark: [bold red]Not enough balance[/bold red]:\n\n"
|
@@ -183,7 +194,7 @@ async def transfer_extrinsic(
|
|
183
194
|
)
|
184
195
|
console.print(
|
185
196
|
f"Balance:\n"
|
186
|
-
f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.
|
197
|
+
f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
187
198
|
)
|
188
199
|
return True
|
189
200
|
|
@@ -111,18 +111,18 @@ class SubtensorInterface:
|
|
111
111
|
return f"Network: {self.network}, Chain: {self.chain_endpoint}"
|
112
112
|
|
113
113
|
async def __aenter__(self):
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
114
|
+
try:
|
115
|
+
with console.status(
|
116
|
+
f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..."
|
117
|
+
):
|
118
118
|
async with self.substrate:
|
119
119
|
return self
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
120
|
+
except TimeoutException:
|
121
|
+
err_console.print(
|
122
|
+
"\n[red]Error[/red]: Timeout occurred connecting to substrate. "
|
123
|
+
f"Verify your chain and network settings: {self}"
|
124
|
+
)
|
125
|
+
raise typer.Exit(code=1)
|
126
126
|
|
127
127
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
128
128
|
await self.substrate.close()
|
@@ -283,7 +283,7 @@ class SubtensorInterface:
|
|
283
283
|
self,
|
284
284
|
runtime_api: str,
|
285
285
|
method: str,
|
286
|
-
params: Optional[Union[list[list[int]], dict[str, int]]],
|
286
|
+
params: Optional[Union[list[list[int]], list[int], dict[str, int]]],
|
287
287
|
block_hash: Optional[str] = None,
|
288
288
|
reuse_block: Optional[bool] = False,
|
289
289
|
) -> Optional[str]:
|
@@ -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
|
@@ -9,7 +10,7 @@ from urllib.parse import urlparse
|
|
9
10
|
|
10
11
|
from bittensor_wallet import Wallet, Keypair
|
11
12
|
from bittensor_wallet.utils import SS58_FORMAT
|
12
|
-
from bittensor_wallet.errors import KeyFileError
|
13
|
+
from bittensor_wallet.errors import KeyFileError, PasswordError
|
13
14
|
from bittensor_wallet import utils
|
14
15
|
from jinja2 import Template
|
15
16
|
from markupsafe import Markup
|
@@ -35,6 +36,8 @@ console = Console()
|
|
35
36
|
err_console = Console(stderr=True)
|
36
37
|
verbose_console = Console(quiet=True)
|
37
38
|
|
39
|
+
UnlockStatus = namedtuple("UnlockStatus", ["success", "message"])
|
40
|
+
|
38
41
|
|
39
42
|
def print_console(message: str, colour: str, title: str, console: Console):
|
40
43
|
console.print(
|
@@ -238,11 +241,14 @@ def get_hotkey_wallets_for_wallet(
|
|
238
241
|
def get_coldkey_wallets_for_path(path: str) -> list[Wallet]:
|
239
242
|
"""Gets all wallets with coldkeys from a given path"""
|
240
243
|
wallet_path = Path(path).expanduser()
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
244
|
+
try:
|
245
|
+
wallets = [
|
246
|
+
Wallet(name=directory.name, path=path)
|
247
|
+
for directory in wallet_path.iterdir()
|
248
|
+
if directory.is_dir()
|
249
|
+
]
|
250
|
+
except FileNotFoundError:
|
251
|
+
wallets = []
|
246
252
|
return wallets
|
247
253
|
|
248
254
|
|
@@ -443,7 +449,7 @@ def get_explorer_url_for_network(
|
|
443
449
|
explorer_opentensor_url = "{root_url}/query/{block_hash}".format(
|
444
450
|
root_url=explorer_root_urls.get("opentensor"), block_hash=block_hash
|
445
451
|
)
|
446
|
-
explorer_taostats_url = "{root_url}/
|
452
|
+
explorer_taostats_url = "{root_url}/hash/{block_hash}".format(
|
447
453
|
root_url=explorer_root_urls.get("taostats"), block_hash=block_hash
|
448
454
|
)
|
449
455
|
explorer_urls["opentensor"] = explorer_opentensor_url
|
@@ -532,7 +538,7 @@ def format_error_message(
|
|
532
538
|
err_docs = error_message.get("docs", [err_description])
|
533
539
|
err_description = err_docs[0] if err_docs else err_description
|
534
540
|
|
535
|
-
return f"Subtensor returned `{err_name}({err_type})` error. This means:
|
541
|
+
return f"Subtensor returned `{err_name}({err_type})` error. This means: '{err_description}'."
|
536
542
|
|
537
543
|
|
538
544
|
def convert_blocks_to_time(blocks: int, block_time: int = 12) -> tuple[int, int, int]:
|
@@ -974,3 +980,39 @@ def retry_prompt(
|
|
974
980
|
return var
|
975
981
|
else:
|
976
982
|
err_console.print(rejection_text)
|
983
|
+
|
984
|
+
|
985
|
+
def unlock_key(
|
986
|
+
wallet: Wallet, unlock_type="cold", print_out: bool = True
|
987
|
+
) -> "UnlockStatus":
|
988
|
+
"""
|
989
|
+
Attempts to decrypt a wallet's coldkey or hotkey
|
990
|
+
Args:
|
991
|
+
wallet: a Wallet object
|
992
|
+
unlock_type: the key type, 'cold' or 'hot'
|
993
|
+
print_out: whether to print out the error message to the err_console
|
994
|
+
|
995
|
+
Returns: UnlockStatus for success status of unlock, with error message if unsuccessful
|
996
|
+
|
997
|
+
"""
|
998
|
+
if unlock_type == "cold":
|
999
|
+
unlocker = "unlock_coldkey"
|
1000
|
+
elif unlock_type == "hot":
|
1001
|
+
unlocker = "unlock_hotkey"
|
1002
|
+
else:
|
1003
|
+
raise ValueError(
|
1004
|
+
f"Invalid unlock type provided: {unlock_type}. Must be 'cold' or 'hot'."
|
1005
|
+
)
|
1006
|
+
try:
|
1007
|
+
getattr(wallet, unlocker)()
|
1008
|
+
return UnlockStatus(True, "")
|
1009
|
+
except PasswordError:
|
1010
|
+
err_msg = f"The password used to decrypt your {unlock_type.capitalize()}key Keyfile is invalid."
|
1011
|
+
if print_out:
|
1012
|
+
err_console.print(f":cross_mark: [red]{err_msg}[/red]")
|
1013
|
+
return UnlockStatus(False, err_msg)
|
1014
|
+
except KeyFileError:
|
1015
|
+
err_msg = f"{unlock_type.capitalize()}key Keyfile is corrupt, non-writable, or non-readable, or non-existent."
|
1016
|
+
if print_out:
|
1017
|
+
err_console.print(f":cross_mark: [red]{err_msg}[/red]")
|
1018
|
+
return UnlockStatus(False, err_msg)
|
@@ -3,7 +3,6 @@ import json
|
|
3
3
|
from typing import Optional, TYPE_CHECKING
|
4
4
|
|
5
5
|
from bittensor_wallet import Wallet
|
6
|
-
from bittensor_wallet.errors import KeyFileError
|
7
6
|
import numpy as np
|
8
7
|
from numpy.typing import NDArray
|
9
8
|
from rich import box
|
@@ -42,6 +41,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
42
41
|
ss58_to_vec_u8,
|
43
42
|
update_metadata_table,
|
44
43
|
group_subnets,
|
44
|
+
unlock_key,
|
45
45
|
)
|
46
46
|
|
47
47
|
if TYPE_CHECKING:
|
@@ -280,9 +280,7 @@ async def burned_register_extrinsic(
|
|
280
280
|
finalization/inclusion, the response is `True`.
|
281
281
|
"""
|
282
282
|
|
283
|
-
|
284
|
-
wallet.unlock_coldkey()
|
285
|
-
except KeyFileError:
|
283
|
+
if not unlock_key(wallet).success:
|
286
284
|
return False
|
287
285
|
|
288
286
|
with console.status(
|
@@ -536,9 +534,7 @@ async def delegate_extrinsic(
|
|
536
534
|
delegate_string = "delegate" if delegate else "undelegate"
|
537
535
|
|
538
536
|
# Decrypt key
|
539
|
-
|
540
|
-
wallet.unlock_coldkey()
|
541
|
-
except KeyFileError:
|
537
|
+
if not unlock_key(wallet).success:
|
542
538
|
return False
|
543
539
|
|
544
540
|
print_verbose("Checking if hotkey is a delegate")
|
@@ -1096,10 +1092,7 @@ async def senate_vote(
|
|
1096
1092
|
return False
|
1097
1093
|
|
1098
1094
|
# Unlock the wallet.
|
1099
|
-
|
1100
|
-
wallet.unlock_hotkey()
|
1101
|
-
wallet.unlock_coldkey()
|
1102
|
-
except KeyFileError:
|
1095
|
+
if not unlock_key(wallet).success and unlock_key(wallet, "hot").success:
|
1103
1096
|
return False
|
1104
1097
|
|
1105
1098
|
console.print(f"Fetching proposals in [dark_orange]network: {subtensor.network}")
|
@@ -1319,10 +1312,7 @@ async def set_take(wallet: Wallet, subtensor: SubtensorInterface, take: float) -
|
|
1319
1312
|
|
1320
1313
|
console.print(f"Setting take on [dark_orange]network: {subtensor.network}")
|
1321
1314
|
# Unlock the wallet.
|
1322
|
-
|
1323
|
-
wallet.unlock_hotkey()
|
1324
|
-
wallet.unlock_coldkey()
|
1325
|
-
except KeyFileError:
|
1315
|
+
if not unlock_key(wallet).success and unlock_key(wallet, "hot").success:
|
1326
1316
|
return False
|
1327
1317
|
|
1328
1318
|
result_ = await _do_set_take()
|
@@ -1720,10 +1710,7 @@ async def nominate(wallet: Wallet, subtensor: SubtensorInterface, prompt: bool):
|
|
1720
1710
|
|
1721
1711
|
console.print(f"Nominating on [dark_orange]network: {subtensor.network}")
|
1722
1712
|
# Unlock the wallet.
|
1723
|
-
|
1724
|
-
wallet.unlock_hotkey()
|
1725
|
-
wallet.unlock_coldkey()
|
1726
|
-
except KeyFileError:
|
1713
|
+
if not unlock_key(wallet).success and unlock_key(wallet, "hot").success:
|
1727
1714
|
return False
|
1728
1715
|
|
1729
1716
|
print_verbose(f"Checking hotkey ({wallet.hotkey_str}) is a delegate")
|