bittensor-cli 8.1.1__py3-none-any.whl → 8.3.0__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 +352 -181
- bittensor_cli/src/__init__.py +4 -2
- bittensor_cli/src/bittensor/async_substrate_interface.py +27 -17
- bittensor_cli/src/bittensor/extrinsics/registration.py +41 -24
- bittensor_cli/src/bittensor/extrinsics/root.py +10 -6
- bittensor_cli/src/bittensor/extrinsics/transfer.py +22 -5
- bittensor_cli/src/bittensor/subtensor_interface.py +16 -14
- bittensor_cli/src/bittensor/utils.py +78 -13
- bittensor_cli/src/commands/root.py +22 -8
- bittensor_cli/src/commands/stake/children_hotkeys.py +16 -8
- bittensor_cli/src/commands/stake/stake.py +7 -2
- bittensor_cli/src/commands/subnets.py +4 -2
- bittensor_cli/src/commands/sudo.py +1 -0
- bittensor_cli/src/commands/wallets.py +75 -65
- bittensor_cli/src/commands/weights.py +48 -45
- {bittensor_cli-8.1.1.dist-info → bittensor_cli-8.3.0.dist-info}/METADATA +2 -2
- bittensor_cli-8.3.0.dist-info/RECORD +31 -0
- bittensor_cli-8.1.1.dist-info/RECORD +0 -31
- {bittensor_cli-8.1.1.dist-info → bittensor_cli-8.3.0.dist-info}/WHEEL +0 -0
- {bittensor_cli-8.1.1.dist-info → bittensor_cli-8.3.0.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.1.1.dist-info → bittensor_cli-8.3.0.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
|
|
@@ -460,6 +460,9 @@ class Runtime:
|
|
460
460
|
self.runtime_config = runtime_config
|
461
461
|
self.metadata = metadata
|
462
462
|
|
463
|
+
def __str__(self):
|
464
|
+
return f"Runtime: {self.chain} | {self.config}"
|
465
|
+
|
463
466
|
@property
|
464
467
|
def implements_scaleinfo(self) -> bool:
|
465
468
|
"""
|
@@ -679,12 +682,12 @@ class Websocket:
|
|
679
682
|
|
680
683
|
async def shutdown(self):
|
681
684
|
async with self._lock:
|
682
|
-
self._receiving_task.cancel()
|
683
685
|
try:
|
686
|
+
self._receiving_task.cancel()
|
684
687
|
await self._receiving_task
|
685
|
-
|
688
|
+
await self.ws.close()
|
689
|
+
except (AttributeError, asyncio.CancelledError):
|
686
690
|
pass
|
687
|
-
await self.ws.close()
|
688
691
|
self.ws = None
|
689
692
|
self._initialized = False
|
690
693
|
self._receiving_task = None
|
@@ -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["rpc_request"][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,6 +16,7 @@ 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
|
@@ -35,6 +36,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
35
36
|
millify,
|
36
37
|
get_human_readable,
|
37
38
|
print_verbose,
|
39
|
+
print_error,
|
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,28 +614,32 @@ 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
|
-
format_error_message(
|
621
|
+
format_error_message(
|
622
|
+
await response.error_message,
|
623
|
+
substrate=subtensor.substrate,
|
624
|
+
),
|
618
625
|
)
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
624
|
-
|
625
|
-
|
626
|
-
|
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}"
|
627
637
|
)
|
628
|
-
|
629
|
-
|
630
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
631
|
-
await asyncio.sleep(0.5)
|
638
|
+
await asyncio.sleep(0.5)
|
632
639
|
|
633
640
|
# Successful registration, final check for neuron and pubkey
|
634
|
-
|
635
|
-
console.print(":satellite: Checking
|
641
|
+
if success:
|
642
|
+
console.print(":satellite: Checking Registration status...")
|
636
643
|
is_registered = await is_hotkey_registered(
|
637
644
|
subtensor,
|
638
645
|
netuid=netuid,
|
@@ -785,7 +792,8 @@ async def run_faucet_extrinsic(
|
|
785
792
|
await response.process_events()
|
786
793
|
if not await response.is_success:
|
787
794
|
err_console.print(
|
788
|
-
f":cross_mark: [red]Failed[/red]:
|
795
|
+
f":cross_mark: [red]Failed[/red]: "
|
796
|
+
f"{format_error_message(await response.error_message, subtensor.substrate)}"
|
789
797
|
)
|
790
798
|
if attempts == max_allowed_attempts:
|
791
799
|
raise MaxAttemptsException
|
@@ -910,6 +918,9 @@ async def _block_solver(
|
|
910
918
|
stop_event.clear()
|
911
919
|
|
912
920
|
solution_queue = Queue()
|
921
|
+
if cuda:
|
922
|
+
num_processes = len(dev_id)
|
923
|
+
|
913
924
|
finished_queues = [Queue() for _ in range(num_processes)]
|
914
925
|
check_block = Lock()
|
915
926
|
|
@@ -919,7 +930,6 @@ async def _block_solver(
|
|
919
930
|
|
920
931
|
if cuda:
|
921
932
|
## Create a worker per CUDA device
|
922
|
-
num_processes = len(dev_id)
|
923
933
|
solvers = [
|
924
934
|
_CUDASolver(
|
925
935
|
i,
|
@@ -1227,8 +1237,14 @@ def _terminate_workers_and_wait_for_exit(
|
|
1227
1237
|
if isinstance(worker, Queue_Type):
|
1228
1238
|
worker.join_thread()
|
1229
1239
|
else:
|
1230
|
-
|
1231
|
-
|
1240
|
+
try:
|
1241
|
+
worker.join(3.0)
|
1242
|
+
except subprocess.TimeoutExpired:
|
1243
|
+
worker.terminate()
|
1244
|
+
try:
|
1245
|
+
worker.close()
|
1246
|
+
except ValueError:
|
1247
|
+
worker.terminate()
|
1232
1248
|
|
1233
1249
|
|
1234
1250
|
# TODO verify this works with async
|
@@ -1626,6 +1642,7 @@ async def swap_hotkey_extrinsic(
|
|
1626
1642
|
try:
|
1627
1643
|
wallet.unlock_coldkey()
|
1628
1644
|
except KeyFileError:
|
1645
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
1629
1646
|
return False
|
1630
1647
|
|
1631
1648
|
if prompt:
|
@@ -20,15 +20,15 @@ import hashlib
|
|
20
20
|
import time
|
21
21
|
from typing import Union, List, TYPE_CHECKING
|
22
22
|
|
23
|
-
from bittensor_wallet import Wallet
|
23
|
+
from bittensor_wallet import Wallet, Keypair
|
24
24
|
from bittensor_wallet.errors import KeyFileError
|
25
25
|
import numpy as np
|
26
26
|
from numpy.typing import NDArray
|
27
27
|
from rich.prompt import Confirm
|
28
28
|
from rich.table import Table, Column
|
29
29
|
from scalecodec import ScaleBytes, U16, Vec
|
30
|
+
from substrateinterface.exceptions import SubstrateRequestException
|
30
31
|
|
31
|
-
from bittensor_wallet import Keypair
|
32
32
|
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
|
33
33
|
from bittensor_cli.src.bittensor.extrinsics.registration import is_hotkey_registered
|
34
34
|
from bittensor_cli.src.bittensor.utils import (
|
@@ -36,6 +36,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
36
36
|
err_console,
|
37
37
|
u16_normalized_float,
|
38
38
|
print_verbose,
|
39
|
+
format_error_message,
|
39
40
|
)
|
40
41
|
|
41
42
|
if TYPE_CHECKING:
|
@@ -308,6 +309,7 @@ async def root_register_extrinsic(
|
|
308
309
|
try:
|
309
310
|
wallet.unlock_coldkey()
|
310
311
|
except KeyFileError:
|
312
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
311
313
|
return False
|
312
314
|
|
313
315
|
print_verbose(f"Checking if hotkey ({wallet.hotkey_str}) is registered on root")
|
@@ -428,6 +430,7 @@ async def set_root_weights_extrinsic(
|
|
428
430
|
try:
|
429
431
|
wallet.unlock_coldkey()
|
430
432
|
except KeyFileError:
|
433
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
431
434
|
return False
|
432
435
|
|
433
436
|
# First convert types.
|
@@ -481,7 +484,6 @@ async def set_root_weights_extrinsic(
|
|
481
484
|
)
|
482
485
|
|
483
486
|
success, error_message = await _do_set_weights()
|
484
|
-
console.print(success, error_message)
|
485
487
|
|
486
488
|
if not wait_for_finalization and not wait_for_inclusion:
|
487
489
|
return True
|
@@ -490,9 +492,11 @@ async def set_root_weights_extrinsic(
|
|
490
492
|
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
491
493
|
return True
|
492
494
|
else:
|
493
|
-
|
495
|
+
fmt_err = format_error_message(error_message, subtensor.substrate)
|
496
|
+
err_console.print(f":cross_mark: [red]Failed[/red]: {fmt_err}")
|
494
497
|
return False
|
495
498
|
|
496
|
-
except
|
497
|
-
|
499
|
+
except SubstrateRequestException as e:
|
500
|
+
fmt_err = format_error_message(e, subtensor.substrate)
|
501
|
+
err_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(fmt_err))
|
498
502
|
return False
|
@@ -3,6 +3,7 @@ import asyncio
|
|
3
3
|
from bittensor_wallet import Wallet
|
4
4
|
from bittensor_wallet.errors import KeyFileError
|
5
5
|
from rich.prompt import Confirm
|
6
|
+
from substrateinterface.exceptions import SubstrateRequestException
|
6
7
|
|
7
8
|
from bittensor_cli.src import NETWORK_EXPLORER_MAP
|
8
9
|
from bittensor_cli.src.bittensor.balances import Balance
|
@@ -14,6 +15,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
14
15
|
format_error_message,
|
15
16
|
get_explorer_url_for_network,
|
16
17
|
is_valid_bittensor_address_or_public_key,
|
18
|
+
print_error,
|
17
19
|
)
|
18
20
|
|
19
21
|
|
@@ -22,6 +24,7 @@ async def transfer_extrinsic(
|
|
22
24
|
wallet: Wallet,
|
23
25
|
destination: str,
|
24
26
|
amount: Balance,
|
27
|
+
transfer_all: bool = False,
|
25
28
|
wait_for_inclusion: bool = True,
|
26
29
|
wait_for_finalization: bool = False,
|
27
30
|
keep_alive: bool = True,
|
@@ -33,6 +36,7 @@ async def transfer_extrinsic(
|
|
33
36
|
:param wallet: Bittensor wallet object to make transfer from.
|
34
37
|
:param destination: Destination public key address (ss58_address or ed25519) of recipient.
|
35
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.
|
36
40
|
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`,
|
37
41
|
or returns `False` if the extrinsic fails to enter the block within the timeout.
|
38
42
|
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning
|
@@ -59,11 +63,11 @@ async def transfer_extrinsic(
|
|
59
63
|
payment_info = await subtensor.substrate.get_payment_info(
|
60
64
|
call=call, keypair=wallet.coldkeypub
|
61
65
|
)
|
62
|
-
except
|
66
|
+
except SubstrateRequestException as e:
|
63
67
|
payment_info = {"partialFee": int(2e7)} # assume 0.02 Tao
|
64
68
|
err_console.print(
|
65
69
|
f":cross_mark: [red]Failed to get payment info[/red]:[bold white]\n"
|
66
|
-
f" {e}[/bold white]\n"
|
70
|
+
f" {format_error_message(e, subtensor.substrate)}[/bold white]\n"
|
67
71
|
f" Defaulting to default transfer fee: {payment_info['partialFee']}"
|
68
72
|
)
|
69
73
|
|
@@ -97,7 +101,11 @@ async def transfer_extrinsic(
|
|
97
101
|
block_hash_ = response.block_hash
|
98
102
|
return True, block_hash_, ""
|
99
103
|
else:
|
100
|
-
return
|
104
|
+
return (
|
105
|
+
False,
|
106
|
+
"",
|
107
|
+
format_error_message(await response.error_message, subtensor.substrate),
|
108
|
+
)
|
101
109
|
|
102
110
|
# Validate destination address.
|
103
111
|
if not is_valid_bittensor_address_or_public_key(destination):
|
@@ -110,6 +118,7 @@ async def transfer_extrinsic(
|
|
110
118
|
try:
|
111
119
|
wallet.unlock_coldkey()
|
112
120
|
except KeyFileError:
|
121
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
113
122
|
return False
|
114
123
|
|
115
124
|
# Check balance.
|
@@ -121,7 +130,9 @@ async def transfer_extrinsic(
|
|
121
130
|
print_verbose("Fetching existential and fee", status)
|
122
131
|
block_hash = await subtensor.substrate.get_chain_head()
|
123
132
|
account_balance_, existential_deposit = await asyncio.gather(
|
124
|
-
subtensor.get_balance(
|
133
|
+
subtensor.get_balance(
|
134
|
+
wallet.coldkeypub.ss58_address, block_hash=block_hash
|
135
|
+
),
|
125
136
|
subtensor.get_existential_deposit(block_hash=block_hash),
|
126
137
|
)
|
127
138
|
account_balance = account_balance_[wallet.coldkeypub.ss58_address]
|
@@ -132,6 +143,12 @@ async def transfer_extrinsic(
|
|
132
143
|
existential_deposit = Balance(0)
|
133
144
|
|
134
145
|
# Check if we have enough balance.
|
146
|
+
if transfer_all is True:
|
147
|
+
amount = account_balance - fee - existential_deposit
|
148
|
+
if amount < Balance(0):
|
149
|
+
print_error("Not enough balance to transfer")
|
150
|
+
return False
|
151
|
+
|
135
152
|
if account_balance < (amount + fee + existential_deposit):
|
136
153
|
err_console.print(
|
137
154
|
":cross_mark: [bold red]Not enough balance[/bold red]:\n\n"
|
@@ -180,7 +197,7 @@ async def transfer_extrinsic(
|
|
180
197
|
)
|
181
198
|
console.print(
|
182
199
|
f"Balance:\n"
|
183
|
-
f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.
|
200
|
+
f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
184
201
|
)
|
185
202
|
return True
|
186
203
|
|
@@ -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]:
|
@@ -927,9 +927,11 @@ class SubtensorInterface:
|
|
927
927
|
if await response.is_success:
|
928
928
|
return True, ""
|
929
929
|
else:
|
930
|
-
return False, format_error_message(
|
930
|
+
return False, format_error_message(
|
931
|
+
await response.error_message, substrate=self.substrate
|
932
|
+
)
|
931
933
|
except SubstrateRequestException as e:
|
932
|
-
return False, e
|
934
|
+
return False, format_error_message(e, substrate=self.substrate)
|
933
935
|
|
934
936
|
async def get_children(self, hotkey, netuid) -> tuple[bool, list, str]:
|
935
937
|
"""
|
@@ -959,7 +961,7 @@ class SubtensorInterface:
|
|
959
961
|
else:
|
960
962
|
return True, [], ""
|
961
963
|
except SubstrateRequestException as e:
|
962
|
-
return False, [],
|
964
|
+
return False, [], format_error_message(e, self.substrate)
|
963
965
|
|
964
966
|
async def get_subnet_hyperparameters(
|
965
967
|
self, netuid: int, block_hash: Optional[str] = None
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import ast
|
1
2
|
import math
|
2
3
|
import os
|
3
4
|
import sqlite3
|
@@ -26,6 +27,9 @@ from bittensor_cli.src.bittensor.balances import Balance
|
|
26
27
|
|
27
28
|
if TYPE_CHECKING:
|
28
29
|
from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
|
30
|
+
from bittensor_cli.src.bittensor.async_substrate_interface import (
|
31
|
+
AsyncSubstrateInterface,
|
32
|
+
)
|
29
33
|
|
30
34
|
console = Console()
|
31
35
|
err_console = Console(stderr=True)
|
@@ -439,7 +443,7 @@ def get_explorer_url_for_network(
|
|
439
443
|
explorer_opentensor_url = "{root_url}/query/{block_hash}".format(
|
440
444
|
root_url=explorer_root_urls.get("opentensor"), block_hash=block_hash
|
441
445
|
)
|
442
|
-
explorer_taostats_url = "{root_url}/
|
446
|
+
explorer_taostats_url = "{root_url}/hash/{block_hash}".format(
|
443
447
|
root_url=explorer_root_urls.get("taostats"), block_hash=block_hash
|
444
448
|
)
|
445
449
|
explorer_urls["opentensor"] = explorer_opentensor_url
|
@@ -448,24 +452,87 @@ def get_explorer_url_for_network(
|
|
448
452
|
return explorer_urls
|
449
453
|
|
450
454
|
|
451
|
-
def format_error_message(
|
455
|
+
def format_error_message(
|
456
|
+
error_message: Union[dict, Exception], substrate: "AsyncSubstrateInterface"
|
457
|
+
) -> str:
|
452
458
|
"""
|
453
|
-
Formats an error message from the Subtensor error information
|
459
|
+
Formats an error message from the Subtensor error information for use in extrinsics.
|
454
460
|
|
455
|
-
:
|
461
|
+
Args:
|
462
|
+
error_message: A dictionary containing the error information from Subtensor, or a SubstrateRequestException
|
463
|
+
containing dictionary literal args.
|
464
|
+
substrate: The initialised SubstrateInterface object to use.
|
456
465
|
|
457
|
-
:
|
466
|
+
Returns:
|
467
|
+
str: A formatted error message string.
|
458
468
|
"""
|
459
|
-
err_type = "UnknownType"
|
460
469
|
err_name = "UnknownError"
|
470
|
+
err_type = "UnknownType"
|
461
471
|
err_description = "Unknown Description"
|
462
472
|
|
473
|
+
if isinstance(error_message, Exception):
|
474
|
+
# generally gotten through SubstrateRequestException args
|
475
|
+
new_error_message = None
|
476
|
+
for arg in error_message.args:
|
477
|
+
try:
|
478
|
+
d = ast.literal_eval(arg)
|
479
|
+
if isinstance(d, dict):
|
480
|
+
if "error" in d:
|
481
|
+
new_error_message = d["error"]
|
482
|
+
break
|
483
|
+
elif all(x in d for x in ["code", "message", "data"]):
|
484
|
+
new_error_message = d
|
485
|
+
break
|
486
|
+
except ValueError:
|
487
|
+
pass
|
488
|
+
if new_error_message is None:
|
489
|
+
return_val = " ".join(error_message.args)
|
490
|
+
return f"Subtensor returned: {return_val}"
|
491
|
+
else:
|
492
|
+
error_message = new_error_message
|
493
|
+
|
463
494
|
if isinstance(error_message, dict):
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
495
|
+
# subtensor error structure
|
496
|
+
if (
|
497
|
+
error_message.get("code")
|
498
|
+
and error_message.get("message")
|
499
|
+
and error_message.get("data")
|
500
|
+
):
|
501
|
+
err_name = "SubstrateRequestException"
|
502
|
+
err_type = error_message.get("message", "")
|
503
|
+
err_data = error_message.get("data", "")
|
504
|
+
|
505
|
+
# subtensor custom error marker
|
506
|
+
if err_data.startswith("Custom error:") and substrate:
|
507
|
+
if substrate.metadata:
|
508
|
+
try:
|
509
|
+
pallet = substrate.metadata.get_metadata_pallet(
|
510
|
+
"SubtensorModule"
|
511
|
+
)
|
512
|
+
error_index = int(err_data.split("Custom error:")[-1])
|
513
|
+
|
514
|
+
error_dict = pallet.errors[error_index].value
|
515
|
+
err_type = error_dict.get("message", err_type)
|
516
|
+
err_docs = error_dict.get("docs", [])
|
517
|
+
err_description = err_docs[0] if err_docs else err_description
|
518
|
+
except (AttributeError, IndexError):
|
519
|
+
err_console.print(
|
520
|
+
"Substrate pallets data unavailable. This is usually caused by an uninitialized substrate."
|
521
|
+
)
|
522
|
+
else:
|
523
|
+
err_description = err_data
|
524
|
+
|
525
|
+
elif (
|
526
|
+
error_message.get("type")
|
527
|
+
and error_message.get("name")
|
528
|
+
and error_message.get("docs")
|
529
|
+
):
|
530
|
+
err_type = error_message.get("type", err_type)
|
531
|
+
err_name = error_message.get("name", err_name)
|
532
|
+
err_docs = error_message.get("docs", [err_description])
|
533
|
+
err_description = err_docs[0] if err_docs else err_description
|
534
|
+
|
535
|
+
return f"Subtensor returned `{err_name}({err_type})` error. This means: '{err_description}'."
|
469
536
|
|
470
537
|
|
471
538
|
def convert_blocks_to_time(blocks: int, block_time: int = 12) -> tuple[int, int, int]:
|
@@ -877,8 +944,6 @@ def validate_chain_endpoint(endpoint_url) -> tuple[bool, str]:
|
|
877
944
|
)
|
878
945
|
if not parsed.netloc:
|
879
946
|
return False, "Invalid URL passed as the endpoint"
|
880
|
-
if not parsed.port:
|
881
|
-
return False, "No port specified in the URL"
|
882
947
|
return True, ""
|
883
948
|
|
884
949
|
|