Skip to content

Commit 247d92a

Browse files
thewhalekingbasfromanRomancamfairchild
authored
Handle new PasswordError from btwallet (#2406)
* Handles new PasswordError from btwallet. * Ruff, docstrings. * Fixes hotkey unlock rather than coldkey * Added unit test. * Added unit test. Add integration test. * Removed coldkeypub * Opinions. * More tests * grammar * Update test_utils.py * bump up btwallet version * fix wording * fix wording * Update tests/unit_tests/utils/test_utils.py Co-authored-by: Cameron Fairchild <[email protected]> --------- Co-authored-by: Roman <[email protected]> Co-authored-by: Roman <[email protected]> Co-authored-by: Cameron Fairchild <[email protected]>
1 parent f4a9c48 commit 247d92a

File tree

11 files changed

+169
-54
lines changed

11 files changed

+169
-54
lines changed

bittensor/core/extrinsics/async_registration.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@
2121
import numpy as np
2222
from Crypto.Hash import keccak
2323
from bittensor_wallet import Wallet
24-
from bittensor_wallet.errors import KeyFileError
2524
from rich.console import Console
2625
from rich.status import Status
2726
from substrateinterface.exceptions import SubstrateRequestException
2827

2928
from bittensor.core.chain_data import NeuronInfo
30-
from bittensor.utils import format_error_message
29+
from bittensor.utils import format_error_message, unlock_key
3130
from bittensor.utils.btlogging import logging
3231
from bittensor.utils.formatting import millify, get_human_readable
3332

@@ -673,10 +672,8 @@ async def run_faucet_extrinsic(
673672
return False, "Requires torch"
674673

675674
# Unlock coldkey
676-
try:
677-
wallet.unlock_coldkey()
678-
except KeyFileError:
679-
return False, "There was an error unlocking your coldkey"
675+
if not (unlock := unlock_key(wallet)).success:
676+
return False, unlock.message
680677

681678
# Get previous balance.
682679
old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)

bittensor/core/extrinsics/async_root.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@
44

55
import numpy as np
66
from bittensor_wallet import Wallet
7-
from bittensor_wallet.errors import KeyFileError
87
from numpy.typing import NDArray
98
from substrateinterface.exceptions import SubstrateRequestException
109

11-
from bittensor.utils import u16_normalized_float, format_error_message
10+
from bittensor.utils import u16_normalized_float, format_error_message, unlock_key
1211
from bittensor.utils.btlogging import logging
1312
from bittensor.utils.weight_utils import (
1413
normalize_max_weight,
@@ -63,10 +62,8 @@ async def root_register_extrinsic(
6362
`True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, the response is `True`.
6463
"""
6564

66-
try:
67-
wallet.unlock_coldkey()
68-
except KeyFileError:
69-
logging.error("Error decrypting coldkey (possibly incorrect password)")
65+
if not (unlock := unlock_key(wallet)).success:
66+
logging.error(unlock.message)
7067
return False
7168

7269
logging.debug(
@@ -182,10 +179,8 @@ async def _do_set_weights():
182179
logging.error("Your hotkey is not registered to the root network.")
183180
return False
184181

185-
try:
186-
wallet.unlock_coldkey()
187-
except KeyFileError:
188-
logging.error("Error decrypting coldkey (possibly incorrect password).")
182+
if not (unlock := unlock_key(wallet)).success:
183+
logging.error(unlock.message)
189184
return False
190185

191186
# First convert types.

bittensor/core/extrinsics/async_transfer.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
from typing import TYPE_CHECKING
33

44
from bittensor_wallet import Wallet
5-
from bittensor_wallet.errors import KeyFileError
65
from substrateinterface.exceptions import SubstrateRequestException
76

87
from bittensor.core.settings import NETWORK_EXPLORER_MAP
98
from bittensor.utils import (
109
format_error_message,
1110
get_explorer_url_for_network,
1211
is_valid_bittensor_address_or_public_key,
12+
unlock_key,
1313
)
1414
from bittensor.utils.balance import Balance
1515
from bittensor.utils.btlogging import logging
@@ -116,10 +116,8 @@ async def do_transfer() -> tuple[bool, str, str]:
116116
return False
117117
logging.info(f"Initiating transfer on network: {subtensor.network}")
118118
# Unlock wallet coldkey.
119-
try:
120-
wallet.unlock_coldkey()
121-
except KeyFileError:
122-
logging.error("Error decrypting coldkey (possibly incorrect password)")
119+
if not (unlock := unlock_key(wallet)).success:
120+
logging.error(unlock.message)
123121
return False
124122

125123
# Check balance.

bittensor/core/extrinsics/registration.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818
import time
1919
from typing import Union, Optional, TYPE_CHECKING
2020

21-
from bittensor_wallet.errors import KeyFileError
2221

23-
from bittensor.utils import format_error_message
22+
from bittensor.utils import format_error_message, unlock_key
2423
from bittensor.utils.btlogging import logging
2524
from bittensor.utils.networking import ensure_connected
2625
from bittensor.utils.registration import (
@@ -347,13 +346,10 @@ def burned_register_extrinsic(
347346
)
348347
return False
349348

350-
try:
351-
wallet.unlock_coldkey()
352-
except KeyFileError:
353-
logging.error(
354-
":cross_mark: <red>Keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid.</red>"
355-
)
349+
if not (unlock := unlock_key(wallet)).success:
350+
logging.error(unlock.message)
356351
return False
352+
357353
logging.info(
358354
f":satellite: <magenta>Checking Account on subnet</magenta> <blue>{netuid}</blue><magenta> ...</magenta>"
359355
)

bittensor/core/extrinsics/root.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,10 @@
22
from typing import Optional, Union, TYPE_CHECKING
33

44
import numpy as np
5-
from bittensor_wallet.errors import KeyFileError
65
from numpy.typing import NDArray
76

87
from bittensor.core.settings import version_as_int
9-
from bittensor.utils import format_error_message, weight_utils
8+
from bittensor.utils import format_error_message, weight_utils, unlock_key
109
from bittensor.utils.btlogging import logging
1110
from bittensor.utils.networking import ensure_connected
1211
from bittensor.utils.registration import torch, legacy_torch_api_compat
@@ -71,12 +70,8 @@ def root_register_extrinsic(
7170
success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``.
7271
"""
7372

74-
try:
75-
wallet.unlock_coldkey()
76-
except KeyFileError:
77-
logging.error(
78-
"<red>Keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid.</red>"
79-
)
73+
if not (unlock := unlock_key(wallet)).success:
74+
logging.error(unlock.message)
8075
return False
8176

8277
is_registered = subtensor.is_hotkey_registered(
@@ -199,13 +194,8 @@ def set_root_weights_extrinsic(
199194
Returns:
200195
success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``.
201196
"""
202-
203-
try:
204-
wallet.unlock_coldkey()
205-
except KeyFileError:
206-
logging.error(
207-
":cross_mark: <red>Keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid.</red>"
208-
)
197+
if not (unlock := unlock_key(wallet)).success:
198+
logging.error(unlock.message)
209199
return False
210200

211201
# First convert types.

bittensor/core/extrinsics/serving.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from bittensor.core.errors import MetadataError
2121
from bittensor.core.extrinsics.utils import submit_extrinsic
2222
from bittensor.core.settings import version_as_int
23-
from bittensor.utils import format_error_message, networking as net
23+
from bittensor.utils import format_error_message, networking as net, unlock_key
2424
from bittensor.utils.btlogging import logging
2525
from bittensor.utils.networking import ensure_connected
2626

@@ -109,7 +109,10 @@ def serve_extrinsic(
109109
success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``.
110110
"""
111111
# Decrypt hotkey
112-
wallet.unlock_hotkey()
112+
if not (unlock := unlock_key(wallet, "hotkey")).success:
113+
logging.error(unlock.message)
114+
return False
115+
113116
params: "AxonServeCallParams" = {
114117
"version": version_as_int,
115118
"ip": net.ip_to_int(ip),
@@ -192,8 +195,9 @@ def serve_axon_extrinsic(
192195
Returns:
193196
success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``.
194197
"""
195-
axon.wallet.unlock_hotkey()
196-
axon.wallet.unlock_coldkeypub()
198+
if not (unlock := unlock_key(axon.wallet, "hotkey")).success:
199+
logging.error(unlock.message)
200+
return False
197201
external_port = axon.external_port
198202

199203
# ---- Get external ip ----
@@ -254,7 +258,9 @@ def publish_metadata(
254258
MetadataError: If there is an error in submitting the extrinsic or if the response from the blockchain indicates failure.
255259
"""
256260

257-
wallet.unlock_hotkey()
261+
if not (unlock := unlock_key(wallet, "hotkey")).success:
262+
logging.error(unlock.message)
263+
return False
258264

259265
with self.substrate as substrate:
260266
call = substrate.compose_call(

bittensor/core/extrinsics/transfer.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
get_explorer_url_for_network,
2424
format_error_message,
2525
is_valid_bittensor_address_or_public_key,
26+
unlock_key,
2627
)
2728
from bittensor.utils.balance import Balance
2829
from bittensor.utils.btlogging import logging
@@ -120,8 +121,9 @@ def transfer_extrinsic(
120121
# Convert bytes to hex string.
121122
dest = "0x" + dest.hex()
122123

123-
# Unlock wallet coldkey.
124-
wallet.unlock_coldkey()
124+
if not (unlock := unlock_key(wallet)).success:
125+
logging.error(unlock.message)
126+
return False
125127

126128
# Convert to bittensor.Balance
127129
if not isinstance(amount, Balance):

bittensor/utils/__init__.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,35 @@
1515
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
1616
# DEALINGS IN THE SOFTWARE.
1717

18-
from urllib.parse import urlparse
1918
import ast
19+
from collections import namedtuple
2020
import hashlib
2121
from typing import Any, Literal, Union, Optional, TYPE_CHECKING
22+
from urllib.parse import urlparse
2223

2324
import scalecodec
2425
from bittensor_wallet import Keypair
2526
from substrateinterface.utils import ss58
2627

2728
from bittensor.core.settings import SS58_FORMAT
2829
from bittensor.utils.btlogging import logging
30+
from bittensor_wallet.errors import KeyFileError, PasswordError
2931
from .registration import torch, use_torch
3032
from .version import version_checking, check_version, VersionCheckError
3133

3234
if TYPE_CHECKING:
3335
from bittensor.utils.async_substrate_interface import AsyncSubstrateInterface
3436
from substrateinterface import SubstrateInterface
37+
from bittensor_wallet import Wallet
3538

3639
RAOPERTAO = 1e9
3740
U16_MAX = 65535
3841
U64_MAX = 18446744073709551615
3942

4043

44+
UnlockStatus = namedtuple("UnlockStatus", ["success", "message"])
45+
46+
4147
def ss58_to_vec_u8(ss58_address: str) -> list[int]:
4248
ss58_bytes: bytes = ss58_address_to_bytes(ss58_address)
4349
encoded_address: list[int] = [int(byte) for byte in ss58_bytes]
@@ -370,3 +376,30 @@ def validate_chain_endpoint(endpoint_url: str) -> tuple[bool, str]:
370376
if not parsed.netloc:
371377
return False, "Invalid URL passed as the endpoint"
372378
return True, ""
379+
380+
381+
def unlock_key(wallet: "Wallet", unlock_type="coldkey") -> "UnlockStatus":
382+
"""
383+
Attempts to decrypt a wallet's coldkey or hotkey
384+
Args:
385+
wallet: a Wallet object
386+
unlock_type: the key type, 'coldkey' or 'hotkey'
387+
Returns: UnlockStatus for success status of unlock, with error message if unsuccessful
388+
"""
389+
if unlock_type == "coldkey":
390+
unlocker = "unlock_coldkey"
391+
elif unlock_type == "hotkey":
392+
unlocker = "unlock_hotkey"
393+
else:
394+
raise ValueError(
395+
f"Invalid unlock type provided: {unlock_type}. Must be 'coldkey' or 'hotkey'."
396+
)
397+
try:
398+
getattr(wallet, unlocker)()
399+
return UnlockStatus(True, "")
400+
except PasswordError:
401+
err_msg = f"The password used to decrypt your {unlock_type.capitalize()} keyfile is invalid."
402+
return UnlockStatus(False, err_msg)
403+
except KeyFileError:
404+
err_msg = f"{unlock_type.capitalize()} keyfile is corrupt, non-writable, or non-readable, or non-existent."
405+
return UnlockStatus(False, err_msg)

requirements/prod.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ scalecodec==1.2.11
2525
substrate-interface~=1.7.9
2626
uvicorn
2727
websockets>12.0
28-
bittensor-wallet>=2.0.2
28+
bittensor-wallet>=2.1.0
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
import shutil
3+
4+
from bittensor_wallet import Wallet, Keyfile, Keypair
5+
import pytest
6+
7+
from bittensor import utils
8+
9+
10+
def test_unlock_key(monkeypatch):
11+
# Ensure path is clean before we run the tests
12+
if os.path.exists("/tmp/bittensor-tests-wallets"):
13+
shutil.rmtree("/tmp/bittensor-tests-wallets")
14+
15+
wallet = Wallet(path="/tmp/bittensor-tests-wallets")
16+
cold_kf = Keyfile("/tmp/bittensor-tests-wallets/default/coldkey", name="default")
17+
kp = Keypair.create_from_mnemonic(
18+
"stool feel open east woman high can denial forget screen trust salt"
19+
)
20+
cold_kf.set_keypair(kp, False, False)
21+
cold_kf.encrypt("1234password1234")
22+
hot_kf = Keyfile("/tmp/bittensor-tests-wallets/default/hotkey", name="default")
23+
hkp = Keypair.create_from_mnemonic(
24+
"stool feel open east woman high can denial forget screen trust salt"
25+
)
26+
hot_kf.set_keypair(hkp, False, False)
27+
hot_kf.encrypt("1234hotkey1234")
28+
monkeypatch.setattr("getpass.getpass", lambda _: "badpassword1234")
29+
result = utils.unlock_key(wallet)
30+
assert result.success is False
31+
monkeypatch.setattr("getpass.getpass", lambda _: "1234password1234")
32+
result = utils.unlock_key(wallet)
33+
assert result.success is True
34+
monkeypatch.setattr("getpass.getpass", lambda _: "badpassword1234")
35+
result = utils.unlock_key(wallet, "hotkey")
36+
assert result.success is False
37+
with pytest.raises(ValueError):
38+
utils.unlock_key(wallet, "mycoldkey")
39+
40+
# Ensure test wallets path is deleted after running tests
41+
if os.path.exists("/tmp/bittensor-tests-wallets"):
42+
shutil.rmtree("/tmp/bittensor-tests-wallets")

0 commit comments

Comments
 (0)