|
1 | | -# The MIT License (MIT) |
2 | | -# Copyright © 2024 Opentensor Foundation |
3 | | -# |
4 | | -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated |
5 | | -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation |
6 | | -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, |
7 | | -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
8 | | -# |
9 | | -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of |
10 | | -# the Software. |
11 | | -# |
12 | | -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO |
13 | | -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
14 | | -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
15 | | -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
16 | | -# DEALINGS IN THE SOFTWARE. |
17 | | - |
18 | | -import asyncio |
19 | 1 | import ast |
20 | | -import base58 |
21 | | -from collections import namedtuple |
22 | 2 | import hashlib |
23 | | -from hashlib import blake2b |
24 | | -from typing import Any, Literal, Union, Optional, TYPE_CHECKING, Coroutine |
| 3 | +from collections import namedtuple |
| 4 | +from typing import Any, Literal, Union, Optional, TYPE_CHECKING |
25 | 5 | from urllib.parse import urlparse |
26 | 6 |
|
27 | 7 | import scalecodec |
| 8 | +from async_substrate_interface.utils import ( |
| 9 | + event_loop_is_running, |
| 10 | + hex_to_bytes, |
| 11 | + get_event_loop, |
| 12 | + execute_coroutine, |
| 13 | +) |
28 | 14 | from bittensor_wallet import Keypair |
| 15 | +from bittensor_wallet.errors import KeyFileError, PasswordError |
| 16 | +from scalecodec import ss58_decode, is_valid_ss58_address as _is_valid_ss58_address |
29 | 17 |
|
30 | 18 | from bittensor.core.settings import SS58_FORMAT |
31 | 19 | from bittensor.utils.btlogging import logging |
32 | | -from bittensor_wallet.errors import KeyFileError, PasswordError |
33 | 20 | from .registration import torch, use_torch |
34 | 21 | from .version import version_checking, check_version, VersionCheckError |
35 | 22 |
|
|
44 | 31 | version_checking = version_checking |
45 | 32 | check_version = check_version |
46 | 33 | VersionCheckError = VersionCheckError |
| 34 | +ss58_decode = ss58_decode |
| 35 | +event_loop_is_running = event_loop_is_running |
| 36 | +hex_to_bytes = hex_to_bytes |
| 37 | +get_event_loop = get_event_loop |
| 38 | +execute_coroutine = execute_coroutine |
47 | 39 |
|
48 | 40 |
|
49 | 41 | RAOPERTAO = 1e9 |
|
54 | 46 | UnlockStatus = namedtuple("UnlockStatus", ["success", "message"]) |
55 | 47 |
|
56 | 48 |
|
57 | | -def ss58_decode(address: str, valid_ss58_format: Optional[int] = None) -> str: |
58 | | - """ |
59 | | - Decodes given SS58 encoded address to an account ID |
60 | | -
|
61 | | - Args: |
62 | | - address: e.g. EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk |
63 | | - valid_ss58_format: the format for what is considered valid |
64 | | -
|
65 | | - Returns: |
66 | | - Decoded string AccountId |
67 | | - """ |
68 | | - |
69 | | - # Check if address is already decoded |
70 | | - if address.startswith("0x"): |
71 | | - return address |
72 | | - |
73 | | - if address == "": |
74 | | - raise ValueError("Empty address provided") |
75 | | - |
76 | | - checksum_prefix = b"SS58PRE" |
77 | | - |
78 | | - address_decoded = base58.b58decode(address) |
79 | | - |
80 | | - if address_decoded[0] & 0b0100_0000: |
81 | | - ss58_format_length = 2 |
82 | | - ss58_format = ( |
83 | | - ((address_decoded[0] & 0b0011_1111) << 2) |
84 | | - | (address_decoded[1] >> 6) |
85 | | - | ((address_decoded[1] & 0b0011_1111) << 8) |
86 | | - ) |
87 | | - else: |
88 | | - ss58_format_length = 1 |
89 | | - ss58_format = address_decoded[0] |
90 | | - |
91 | | - if ss58_format in [46, 47]: |
92 | | - raise ValueError(f"{ss58_format} is a reserved SS58 format") |
93 | | - |
94 | | - if valid_ss58_format is not None and ss58_format != valid_ss58_format: |
95 | | - raise ValueError("Invalid SS58 format") |
96 | | - |
97 | | - # Determine checksum length according to length of address string |
98 | | - if len(address_decoded) in [3, 4, 6, 10]: |
99 | | - checksum_length = 1 |
100 | | - elif len(address_decoded) in [ |
101 | | - 5, |
102 | | - 7, |
103 | | - 11, |
104 | | - 34 + ss58_format_length, |
105 | | - 35 + ss58_format_length, |
106 | | - ]: |
107 | | - checksum_length = 2 |
108 | | - elif len(address_decoded) in [8, 12]: |
109 | | - checksum_length = 3 |
110 | | - elif len(address_decoded) in [9, 13]: |
111 | | - checksum_length = 4 |
112 | | - elif len(address_decoded) in [14]: |
113 | | - checksum_length = 5 |
114 | | - elif len(address_decoded) in [15]: |
115 | | - checksum_length = 6 |
116 | | - elif len(address_decoded) in [16]: |
117 | | - checksum_length = 7 |
118 | | - elif len(address_decoded) in [17]: |
119 | | - checksum_length = 8 |
120 | | - else: |
121 | | - raise ValueError("Invalid address length") |
122 | | - |
123 | | - checksum = blake2b(checksum_prefix + address_decoded[0:-checksum_length]).digest() |
124 | | - |
125 | | - if checksum[0:checksum_length] != address_decoded[-checksum_length:]: |
126 | | - raise ValueError("Invalid checksum") |
127 | | - |
128 | | - return address_decoded[ |
129 | | - ss58_format_length : len(address_decoded) - checksum_length |
130 | | - ].hex() |
131 | | - |
132 | | - |
133 | | -def _is_valid_ss58_address(value: str, valid_ss58_format: Optional[int] = None) -> bool: |
134 | | - """ |
135 | | - Checks if given value is a valid SS58 formatted address, optionally check if address is valid for specified |
136 | | - ss58_format |
137 | | -
|
138 | | - Args: |
139 | | - value: value to checked |
140 | | - valid_ss58_format: if valid_ss58_format is provided the address must be valid for specified ss58_format |
141 | | - (network) as well |
142 | | -
|
143 | | - Returns: |
144 | | - bool result |
145 | | - """ |
146 | | - |
147 | | - # Return False in case a public key is provided |
148 | | - if value.startswith("0x"): |
149 | | - return False |
150 | | - |
151 | | - try: |
152 | | - ss58_decode(value, valid_ss58_format=valid_ss58_format) |
153 | | - except ValueError: |
154 | | - return False |
155 | | - |
156 | | - return True |
157 | | - |
158 | | - |
159 | | -def event_loop_is_running() -> Optional[asyncio.AbstractEventLoop]: |
160 | | - """ |
161 | | - Simple function to check if event loop is running. Returns the loop if it is, otherwise None. |
162 | | - """ |
163 | | - try: |
164 | | - return asyncio.get_running_loop() |
165 | | - except RuntimeError: |
166 | | - return None |
167 | | - |
168 | | - |
169 | 49 | def ss58_to_vec_u8(ss58_address: str) -> list[int]: |
170 | 50 | ss58_bytes: bytes = ss58_address_to_bytes(ss58_address) |
171 | 51 | encoded_address: list[int] = [int(byte) for byte in ss58_bytes] |
@@ -508,48 +388,3 @@ def unlock_key(wallet: "Wallet", unlock_type="coldkey") -> "UnlockStatus": |
508 | 388 | except KeyFileError: |
509 | 389 | err_msg = f"{unlock_type.capitalize()} keyfile is corrupt, non-writable, or non-readable, or non-existent." |
510 | 390 | return UnlockStatus(False, err_msg) |
511 | | - |
512 | | - |
513 | | -def hex_to_bytes(hex_str: str) -> bytes: |
514 | | - """ |
515 | | - Converts a hex-encoded string into bytes. Handles 0x-prefixed and non-prefixed hex-encoded strings. |
516 | | - """ |
517 | | - if hex_str.startswith("0x"): |
518 | | - bytes_result = bytes.fromhex(hex_str[2:]) |
519 | | - else: |
520 | | - bytes_result = bytes.fromhex(hex_str) |
521 | | - return bytes_result |
522 | | - |
523 | | - |
524 | | -def get_event_loop() -> asyncio.AbstractEventLoop: |
525 | | - """ |
526 | | - If an event loop is already running, returns that. Otherwise, creates a new event loop, |
527 | | - and sets it as the main event loop for this thread, returning the newly-created event loop. |
528 | | - """ |
529 | | - if loop := event_loop_is_running(): |
530 | | - event_loop = loop |
531 | | - else: |
532 | | - event_loop = asyncio.get_event_loop() |
533 | | - asyncio.set_event_loop(event_loop) |
534 | | - return event_loop |
535 | | - |
536 | | - |
537 | | -def execute_coroutine( |
538 | | - coroutine: "Coroutine", event_loop: asyncio.AbstractEventLoop = None |
539 | | -): |
540 | | - """ |
541 | | - Helper function to run an asyncio coroutine synchronously. |
542 | | -
|
543 | | - Args: |
544 | | - coroutine (Coroutine): The coroutine to run. |
545 | | - event_loop (AbstractEventLoop): The event loop to use. If `None`, attempts to fetch the already-running |
546 | | - loop. If one is not running, a new loop is created. |
547 | | -
|
548 | | - Returns: |
549 | | - The result of the coroutine execution. |
550 | | - """ |
551 | | - if event_loop: |
552 | | - event_loop = event_loop |
553 | | - else: |
554 | | - event_loop = get_event_loop() |
555 | | - return event_loop.run_until_complete(asyncio.wait_for(coroutine, timeout=None)) |
0 commit comments