meshtensor-cli 9.18.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.
- meshtensor_cli/__init__.py +22 -0
- meshtensor_cli/cli.py +10742 -0
- meshtensor_cli/doc_generation_helper.py +4 -0
- meshtensor_cli/src/__init__.py +1085 -0
- meshtensor_cli/src/commands/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/axon.py +132 -0
- meshtensor_cli/src/commands/crowd/__init__.py +0 -0
- meshtensor_cli/src/commands/crowd/contribute.py +621 -0
- meshtensor_cli/src/commands/crowd/contributors.py +200 -0
- meshtensor_cli/src/commands/crowd/create.py +783 -0
- meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
- meshtensor_cli/src/commands/crowd/refund.py +233 -0
- meshtensor_cli/src/commands/crowd/update.py +418 -0
- meshtensor_cli/src/commands/crowd/utils.py +124 -0
- meshtensor_cli/src/commands/crowd/view.py +991 -0
- meshtensor_cli/src/commands/governance/__init__.py +0 -0
- meshtensor_cli/src/commands/governance/governance.py +794 -0
- meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
- meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
- meshtensor_cli/src/commands/liquidity/utils.py +202 -0
- meshtensor_cli/src/commands/proxy.py +700 -0
- meshtensor_cli/src/commands/stake/__init__.py +0 -0
- meshtensor_cli/src/commands/stake/add.py +799 -0
- meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
- meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
- meshtensor_cli/src/commands/stake/claim.py +770 -0
- meshtensor_cli/src/commands/stake/list.py +738 -0
- meshtensor_cli/src/commands/stake/move.py +1211 -0
- meshtensor_cli/src/commands/stake/remove.py +1466 -0
- meshtensor_cli/src/commands/stake/wizard.py +323 -0
- meshtensor_cli/src/commands/subnets/__init__.py +0 -0
- meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
- meshtensor_cli/src/commands/subnets/price.py +733 -0
- meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
- meshtensor_cli/src/commands/sudo.py +1294 -0
- meshtensor_cli/src/commands/tc/__init__.py +0 -0
- meshtensor_cli/src/commands/tc/tc.py +190 -0
- meshtensor_cli/src/commands/treasury/__init__.py +0 -0
- meshtensor_cli/src/commands/treasury/treasury.py +194 -0
- meshtensor_cli/src/commands/view.py +354 -0
- meshtensor_cli/src/commands/wallets.py +2311 -0
- meshtensor_cli/src/commands/weights.py +467 -0
- meshtensor_cli/src/meshtensor/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/balances.py +313 -0
- meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
- meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
- meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
- meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
- meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
- meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
- meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
- meshtensor_cli/src/meshtensor/minigraph.py +254 -0
- meshtensor_cli/src/meshtensor/networking.py +12 -0
- meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
- meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
- meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
- meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
- meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
- meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
- meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
- meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
- meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
- meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
- meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
- meshtensor_cli/src/meshtensor/utils.py +2007 -0
- meshtensor_cli/version.py +23 -0
- meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
- meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
- meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
- meshtensor_cli-9.18.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,1861 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import binascii
|
|
3
|
+
from contextlib import redirect_stdout
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from datetime import timedelta
|
|
6
|
+
import functools
|
|
7
|
+
import hashlib
|
|
8
|
+
import io
|
|
9
|
+
import math
|
|
10
|
+
import multiprocessing as mp
|
|
11
|
+
from multiprocessing.queues import Queue as Queue_Type
|
|
12
|
+
from multiprocessing import Process, Event, Lock, Array, Value, Queue
|
|
13
|
+
import os
|
|
14
|
+
from queue import Empty, Full
|
|
15
|
+
import random
|
|
16
|
+
import time
|
|
17
|
+
import typing
|
|
18
|
+
from typing import Optional
|
|
19
|
+
import subprocess
|
|
20
|
+
|
|
21
|
+
from async_substrate_interface import AsyncExtrinsicReceipt
|
|
22
|
+
from meshtensor_wallet import Wallet
|
|
23
|
+
from Crypto.Hash import keccak
|
|
24
|
+
import numpy as np
|
|
25
|
+
from rich.console import Console
|
|
26
|
+
from rich.status import Status
|
|
27
|
+
from async_substrate_interface.errors import SubstrateRequestException
|
|
28
|
+
|
|
29
|
+
from meshtensor_cli.src import COLOR_PALETTE
|
|
30
|
+
from meshtensor_cli.src.meshtensor.chain_data import NeuronInfo
|
|
31
|
+
from meshtensor_cli.src.meshtensor.balances import Balance
|
|
32
|
+
from meshtensor_cli.src.meshtensor.utils import (
|
|
33
|
+
confirm_action,
|
|
34
|
+
console,
|
|
35
|
+
print_error,
|
|
36
|
+
print_success,
|
|
37
|
+
format_error_message,
|
|
38
|
+
millify,
|
|
39
|
+
get_human_readable,
|
|
40
|
+
print_verbose,
|
|
41
|
+
print_error,
|
|
42
|
+
unlock_key,
|
|
43
|
+
hex_to_bytes,
|
|
44
|
+
get_hotkey_pub_ss58,
|
|
45
|
+
print_extrinsic_id,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if typing.TYPE_CHECKING:
|
|
49
|
+
from meshtensor_cli.src.meshtensor.meshtensor_interface import MeshtensorInterface
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def use_torch() -> bool:
|
|
53
|
+
"""Force the use of torch over numpy for certain operations."""
|
|
54
|
+
return True if os.getenv("USE_TORCH") == "1" else False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def legacy_torch_api_compat(func: typing.Callable):
|
|
58
|
+
"""
|
|
59
|
+
Convert function operating on numpy Input&Output to legacy torch Input&Output API if `use_torch()` is True.
|
|
60
|
+
|
|
61
|
+
:param func: Function with numpy Input/Output to be decorated.
|
|
62
|
+
|
|
63
|
+
:return: Decorated function
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
@functools.wraps(func)
|
|
67
|
+
def decorated(*args, **kwargs):
|
|
68
|
+
if use_torch():
|
|
69
|
+
# if argument is a Torch tensor, convert it to numpy
|
|
70
|
+
args = [
|
|
71
|
+
arg.cpu().numpy() if isinstance(arg, torch.Tensor) else arg
|
|
72
|
+
for arg in args
|
|
73
|
+
]
|
|
74
|
+
kwargs = {
|
|
75
|
+
key: value.cpu().numpy() if isinstance(value, torch.Tensor) else value
|
|
76
|
+
for key, value in kwargs.items()
|
|
77
|
+
}
|
|
78
|
+
ret = func(*args, **kwargs)
|
|
79
|
+
if use_torch():
|
|
80
|
+
# if return value is a numpy array, convert it to Torch tensor
|
|
81
|
+
if isinstance(ret, np.ndarray):
|
|
82
|
+
ret = torch.from_numpy(ret)
|
|
83
|
+
return ret
|
|
84
|
+
|
|
85
|
+
return decorated
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@functools.cache
|
|
89
|
+
def _get_real_torch():
|
|
90
|
+
try:
|
|
91
|
+
import torch as _real_torch
|
|
92
|
+
except ImportError:
|
|
93
|
+
_real_torch = None
|
|
94
|
+
return _real_torch
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def log_no_torch_error():
|
|
98
|
+
print_error(
|
|
99
|
+
"This command requires torch. You can install torch"
|
|
100
|
+
" with `pip install torch` and run the command again."
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass
|
|
105
|
+
class POWSolution:
|
|
106
|
+
"""A solution to the registration PoW problem."""
|
|
107
|
+
|
|
108
|
+
nonce: int
|
|
109
|
+
block_number: int
|
|
110
|
+
difficulty: int
|
|
111
|
+
seal: bytes
|
|
112
|
+
|
|
113
|
+
async def is_stale(self, meshtensor: "MeshtensorInterface") -> bool:
|
|
114
|
+
"""Returns True if the POW is stale.
|
|
115
|
+
This means the block the POW is solved for is within 3 blocks of the current block.
|
|
116
|
+
"""
|
|
117
|
+
current_block = await meshtensor.substrate.get_block_number(None)
|
|
118
|
+
return self.block_number < current_block - 3
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class RegistrationStatistics:
|
|
123
|
+
"""Statistics for a registration."""
|
|
124
|
+
|
|
125
|
+
time_spent_total: float
|
|
126
|
+
rounds_total: int
|
|
127
|
+
time_average: float
|
|
128
|
+
time_spent: float
|
|
129
|
+
hash_rate_perpetual: float
|
|
130
|
+
hash_rate: float
|
|
131
|
+
difficulty: int
|
|
132
|
+
block_number: int
|
|
133
|
+
block_hash: str
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RegistrationStatisticsLogger:
|
|
137
|
+
"""Logs statistics for a registration."""
|
|
138
|
+
|
|
139
|
+
console: Console
|
|
140
|
+
status: Optional[Status]
|
|
141
|
+
|
|
142
|
+
def __init__(self, console_: Console, output_in_place: bool = True) -> None:
|
|
143
|
+
self.console = console_
|
|
144
|
+
|
|
145
|
+
if output_in_place:
|
|
146
|
+
self.status = self.console.status("Solving")
|
|
147
|
+
else:
|
|
148
|
+
self.status = None
|
|
149
|
+
|
|
150
|
+
def start(self) -> None:
|
|
151
|
+
if self.status is not None:
|
|
152
|
+
self.status.start()
|
|
153
|
+
|
|
154
|
+
def stop(self) -> None:
|
|
155
|
+
if self.status is not None:
|
|
156
|
+
self.status.stop()
|
|
157
|
+
|
|
158
|
+
@classmethod
|
|
159
|
+
def get_status_message(
|
|
160
|
+
cls, stats: RegistrationStatistics, verbose: bool = False
|
|
161
|
+
) -> str:
|
|
162
|
+
"""
|
|
163
|
+
Provides a message of the current status of the block solving as a str for a logger or stdout
|
|
164
|
+
"""
|
|
165
|
+
message = (
|
|
166
|
+
"Solving\n"
|
|
167
|
+
+ f"Time Spent (total): [bold white]{timedelta(seconds=stats.time_spent_total)}[/bold white]\n"
|
|
168
|
+
+ (
|
|
169
|
+
f"Time Spent This Round: {timedelta(seconds=stats.time_spent)}\n"
|
|
170
|
+
+ f"Time Spent Average: {timedelta(seconds=stats.time_average)}\n"
|
|
171
|
+
if verbose
|
|
172
|
+
else ""
|
|
173
|
+
)
|
|
174
|
+
+ f"Registration Difficulty: [bold white]{millify(stats.difficulty)}[/bold white]\n"
|
|
175
|
+
+ f"Iters (Inst/Perp): [bold white]{get_human_readable(stats.hash_rate, 'H')}/s / "
|
|
176
|
+
+ f"{get_human_readable(stats.hash_rate_perpetual, 'H')}/s[/bold white]\n"
|
|
177
|
+
+ f"Block Number: [bold white]{stats.block_number}[/bold white]\n"
|
|
178
|
+
+ f"Block Hash: [bold white]{stats.block_hash.encode('utf-8')}[/bold white]\n"
|
|
179
|
+
)
|
|
180
|
+
return message
|
|
181
|
+
|
|
182
|
+
def update(self, stats: RegistrationStatistics, verbose: bool = False) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Passes the current status to the logger
|
|
185
|
+
"""
|
|
186
|
+
if self.status is not None:
|
|
187
|
+
self.status.update(self.get_status_message(stats, verbose=verbose))
|
|
188
|
+
else:
|
|
189
|
+
self.console.log(self.get_status_message(stats, verbose=verbose))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class _SolverBase(Process):
|
|
193
|
+
"""
|
|
194
|
+
A process that solves the registration PoW problem.
|
|
195
|
+
|
|
196
|
+
:param proc_num: The number of the process being created.
|
|
197
|
+
:param num_proc: The total number of processes running.
|
|
198
|
+
:param update_interval: The number of nonces to try to solve before checking for a new block.
|
|
199
|
+
:param finished_queue: The queue to put the process number when a process finishes each update_interval.
|
|
200
|
+
Used for calculating the average time per update_interval across all processes.
|
|
201
|
+
:param solution_queue: The queue to put the solution the process has found during the pow solve.
|
|
202
|
+
:param stop_event: The event to set by the main process when all the solver processes should stop.
|
|
203
|
+
The solver process will check for the event after each update_interval.
|
|
204
|
+
The solver process will stop when the event is set.
|
|
205
|
+
Used to stop the solver processes when a solution is found.
|
|
206
|
+
:param curr_block: The array containing this process's current block hash.
|
|
207
|
+
The main process will set the array to the new block hash when a new block is finalized in the
|
|
208
|
+
network. The solver process will get the new block hash from this array when newBlockEvent is set
|
|
209
|
+
:param curr_block_num: The value containing this process's current block number.
|
|
210
|
+
The main process will set the value to the new block number when a new block is finalized in
|
|
211
|
+
the network. The solver process will get the new block number from this value when
|
|
212
|
+
new_block_event is set.
|
|
213
|
+
:param curr_diff: The array containing this process's current difficulty. The main process will set the array to
|
|
214
|
+
the new difficulty when a new block is finalized in the network. The solver process will get the
|
|
215
|
+
new difficulty from this array when newBlockEvent is set.
|
|
216
|
+
:param check_block: The lock to prevent this process from getting the new block data while the main process is
|
|
217
|
+
updating the data.
|
|
218
|
+
:param limit: The limit of the pow solve for a valid solution.
|
|
219
|
+
|
|
220
|
+
:var new_block_event: The event to set by the main process when a new block is finalized in the network.
|
|
221
|
+
The solver process will check for the event after each update_interval.
|
|
222
|
+
The solver process will get the new block hash and difficulty and start solving for a new
|
|
223
|
+
nonce.
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
proc_num: int
|
|
227
|
+
num_proc: int
|
|
228
|
+
update_interval: int
|
|
229
|
+
finished_queue: Queue_Type
|
|
230
|
+
solution_queue: Queue_Type
|
|
231
|
+
new_block_event: Event
|
|
232
|
+
stop_event: Event
|
|
233
|
+
hotkey_bytes: bytes
|
|
234
|
+
curr_block: Array
|
|
235
|
+
curr_block_num: Value
|
|
236
|
+
curr_diff: Array
|
|
237
|
+
check_block: Lock
|
|
238
|
+
limit: int
|
|
239
|
+
|
|
240
|
+
def __init__(
|
|
241
|
+
self,
|
|
242
|
+
proc_num,
|
|
243
|
+
num_proc,
|
|
244
|
+
update_interval,
|
|
245
|
+
finished_queue,
|
|
246
|
+
solution_queue,
|
|
247
|
+
stop_event,
|
|
248
|
+
curr_block,
|
|
249
|
+
curr_block_num,
|
|
250
|
+
curr_diff,
|
|
251
|
+
check_block,
|
|
252
|
+
limit,
|
|
253
|
+
):
|
|
254
|
+
Process.__init__(self, daemon=True)
|
|
255
|
+
self.proc_num = proc_num
|
|
256
|
+
self.num_proc = num_proc
|
|
257
|
+
self.update_interval = update_interval
|
|
258
|
+
self.finished_queue = finished_queue
|
|
259
|
+
self.solution_queue = solution_queue
|
|
260
|
+
self.new_block_event = Event()
|
|
261
|
+
self.new_block_event.clear()
|
|
262
|
+
self.curr_block = curr_block
|
|
263
|
+
self.curr_block_num = curr_block_num
|
|
264
|
+
self.curr_diff = curr_diff
|
|
265
|
+
self.check_block = check_block
|
|
266
|
+
self.stop_event = stop_event
|
|
267
|
+
self.limit = limit
|
|
268
|
+
|
|
269
|
+
def run(self):
|
|
270
|
+
raise NotImplementedError("_SolverBase is an abstract class")
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def create_shared_memory() -> tuple[Array, Value, Array]:
|
|
274
|
+
"""Creates shared memory for the solver processes to use."""
|
|
275
|
+
curr_block = Array("h", 32, lock=True) # byte array
|
|
276
|
+
curr_block_num = Value("i", 0, lock=True) # int
|
|
277
|
+
curr_diff = Array("Q", [0, 0], lock=True) # [high, low]
|
|
278
|
+
|
|
279
|
+
return curr_block, curr_block_num, curr_diff
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class _Solver(_SolverBase):
|
|
283
|
+
"""
|
|
284
|
+
Performs POW Solution
|
|
285
|
+
"""
|
|
286
|
+
|
|
287
|
+
def run(self):
|
|
288
|
+
block_number: int
|
|
289
|
+
block_and_hotkey_hash_bytes: bytes
|
|
290
|
+
block_difficulty: int
|
|
291
|
+
nonce_limit = int(math.pow(2, 64)) - 1
|
|
292
|
+
|
|
293
|
+
# Start at random nonce
|
|
294
|
+
nonce_start = random.randint(0, nonce_limit)
|
|
295
|
+
nonce_end = nonce_start + self.update_interval
|
|
296
|
+
while not self.stop_event.is_set():
|
|
297
|
+
if self.new_block_event.is_set():
|
|
298
|
+
with self.check_block:
|
|
299
|
+
block_number = self.curr_block_num.value
|
|
300
|
+
block_and_hotkey_hash_bytes = bytes(self.curr_block)
|
|
301
|
+
block_difficulty = _registration_diff_unpack(self.curr_diff)
|
|
302
|
+
|
|
303
|
+
self.new_block_event.clear()
|
|
304
|
+
|
|
305
|
+
# Do a block of nonces
|
|
306
|
+
solution = _solve_for_nonce_block(
|
|
307
|
+
nonce_start,
|
|
308
|
+
nonce_end,
|
|
309
|
+
block_and_hotkey_hash_bytes,
|
|
310
|
+
block_difficulty,
|
|
311
|
+
self.limit,
|
|
312
|
+
block_number,
|
|
313
|
+
)
|
|
314
|
+
if solution is not None:
|
|
315
|
+
self.solution_queue.put(solution)
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
# Send time
|
|
319
|
+
self.finished_queue.put_nowait(self.proc_num)
|
|
320
|
+
except Full:
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
nonce_start = random.randint(0, nonce_limit)
|
|
324
|
+
nonce_start = nonce_start % nonce_limit
|
|
325
|
+
nonce_end = nonce_start + self.update_interval
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class _CUDASolver(_SolverBase):
|
|
329
|
+
"""
|
|
330
|
+
Performs POW Solution using CUDA
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
dev_id: int
|
|
334
|
+
tpb: int
|
|
335
|
+
|
|
336
|
+
def __init__(
|
|
337
|
+
self,
|
|
338
|
+
proc_num,
|
|
339
|
+
num_proc,
|
|
340
|
+
update_interval,
|
|
341
|
+
finished_queue,
|
|
342
|
+
solution_queue,
|
|
343
|
+
stop_event,
|
|
344
|
+
curr_block,
|
|
345
|
+
curr_block_num,
|
|
346
|
+
curr_diff,
|
|
347
|
+
check_block,
|
|
348
|
+
limit,
|
|
349
|
+
dev_id: int,
|
|
350
|
+
tpb: int,
|
|
351
|
+
):
|
|
352
|
+
super().__init__(
|
|
353
|
+
proc_num,
|
|
354
|
+
num_proc,
|
|
355
|
+
update_interval,
|
|
356
|
+
finished_queue,
|
|
357
|
+
solution_queue,
|
|
358
|
+
stop_event,
|
|
359
|
+
curr_block,
|
|
360
|
+
curr_block_num,
|
|
361
|
+
curr_diff,
|
|
362
|
+
check_block,
|
|
363
|
+
limit,
|
|
364
|
+
)
|
|
365
|
+
self.dev_id = dev_id
|
|
366
|
+
self.tpb = tpb
|
|
367
|
+
|
|
368
|
+
def run(self):
|
|
369
|
+
block_number: int = 0 # dummy value
|
|
370
|
+
block_and_hotkey_hash_bytes: bytes = b"0" * 32 # dummy value
|
|
371
|
+
block_difficulty: int = int(math.pow(2, 64)) - 1 # dummy value
|
|
372
|
+
nonce_limit = int(math.pow(2, 64)) - 1 # U64MAX
|
|
373
|
+
|
|
374
|
+
# Start at random nonce
|
|
375
|
+
nonce_start = random.randint(0, nonce_limit)
|
|
376
|
+
while not self.stop_event.is_set():
|
|
377
|
+
if self.new_block_event.is_set():
|
|
378
|
+
with self.check_block:
|
|
379
|
+
block_number = self.curr_block_num.value
|
|
380
|
+
block_and_hotkey_hash_bytes = bytes(self.curr_block)
|
|
381
|
+
block_difficulty = _registration_diff_unpack(self.curr_diff)
|
|
382
|
+
|
|
383
|
+
self.new_block_event.clear()
|
|
384
|
+
|
|
385
|
+
# Do a block of nonces
|
|
386
|
+
solution = _solve_for_nonce_block_cuda(
|
|
387
|
+
nonce_start,
|
|
388
|
+
self.update_interval,
|
|
389
|
+
block_and_hotkey_hash_bytes,
|
|
390
|
+
block_difficulty,
|
|
391
|
+
self.limit,
|
|
392
|
+
block_number,
|
|
393
|
+
self.dev_id,
|
|
394
|
+
self.tpb,
|
|
395
|
+
)
|
|
396
|
+
if solution is not None:
|
|
397
|
+
self.solution_queue.put(solution)
|
|
398
|
+
|
|
399
|
+
try:
|
|
400
|
+
# Signal that a nonce_block was finished using queue
|
|
401
|
+
# send our proc_num
|
|
402
|
+
self.finished_queue.put(self.proc_num)
|
|
403
|
+
except Full:
|
|
404
|
+
pass
|
|
405
|
+
|
|
406
|
+
# increase nonce by number of nonces processed
|
|
407
|
+
nonce_start += self.update_interval * self.tpb
|
|
408
|
+
nonce_start = nonce_start % nonce_limit
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class LazyLoadedTorch:
|
|
412
|
+
def __bool__(self):
|
|
413
|
+
return bool(_get_real_torch())
|
|
414
|
+
|
|
415
|
+
def __getattr__(self, name):
|
|
416
|
+
if real_torch := _get_real_torch():
|
|
417
|
+
return getattr(real_torch, name)
|
|
418
|
+
else:
|
|
419
|
+
log_no_torch_error()
|
|
420
|
+
raise ImportError("torch not installed")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
if typing.TYPE_CHECKING:
|
|
424
|
+
import torch
|
|
425
|
+
else:
|
|
426
|
+
torch = LazyLoadedTorch()
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class MaxSuccessException(Exception):
|
|
430
|
+
"""
|
|
431
|
+
Raised when the POW Solver has reached the max number of successful solutions
|
|
432
|
+
"""
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class MaxAttemptsException(Exception):
|
|
436
|
+
"""
|
|
437
|
+
Raised when the POW Solver has reached the max number of attempts
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
async def is_hotkey_registered(
|
|
442
|
+
meshtensor: "MeshtensorInterface", netuid: int, hotkey_ss58: str
|
|
443
|
+
) -> bool:
|
|
444
|
+
"""Checks to see if the hotkey is registered on a given netuid"""
|
|
445
|
+
_result = await meshtensor.query(
|
|
446
|
+
module="MeshtensorModule",
|
|
447
|
+
storage_function="Uids",
|
|
448
|
+
params=[netuid, hotkey_ss58],
|
|
449
|
+
)
|
|
450
|
+
if _result is not None:
|
|
451
|
+
return True
|
|
452
|
+
else:
|
|
453
|
+
return False
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
async def register_extrinsic(
|
|
457
|
+
meshtensor: "MeshtensorInterface",
|
|
458
|
+
wallet: Wallet,
|
|
459
|
+
netuid: int,
|
|
460
|
+
wait_for_inclusion: bool = False,
|
|
461
|
+
wait_for_finalization: bool = True,
|
|
462
|
+
prompt: bool = False,
|
|
463
|
+
decline: bool = False,
|
|
464
|
+
quiet: bool = False,
|
|
465
|
+
max_allowed_attempts: int = 3,
|
|
466
|
+
output_in_place: bool = True,
|
|
467
|
+
cuda: bool = False,
|
|
468
|
+
dev_id: typing.Union[list[int], int] = 0,
|
|
469
|
+
tpb: int = 256,
|
|
470
|
+
num_processes: Optional[int] = None,
|
|
471
|
+
update_interval: Optional[int] = None,
|
|
472
|
+
log_verbose: bool = False,
|
|
473
|
+
) -> bool:
|
|
474
|
+
"""Registers the wallet to the chain.
|
|
475
|
+
|
|
476
|
+
:param meshtensor: initialized MeshtensorInterface object to use for chain interactions
|
|
477
|
+
:param wallet: Meshtensor wallet object.
|
|
478
|
+
:param netuid: The ``netuid`` of the subnet to register on.
|
|
479
|
+
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
|
480
|
+
`False` if the extrinsic fails to enter the block within the timeout.
|
|
481
|
+
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
|
482
|
+
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
|
483
|
+
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
|
484
|
+
:param max_allowed_attempts: Maximum number of attempts to register the wallet.
|
|
485
|
+
:param output_in_place: Whether the POW solving should be outputted to the console as it goes along.
|
|
486
|
+
:param cuda: If `True`, the wallet should be registered using CUDA device(s).
|
|
487
|
+
:param dev_id: The CUDA device id to use, or a list of device ids.
|
|
488
|
+
:param tpb: The number of threads per block (CUDA).
|
|
489
|
+
:param num_processes: The number of processes to use to register.
|
|
490
|
+
:param update_interval: The number of nonces to solve between updates.
|
|
491
|
+
:param log_verbose: If `True`, the registration process will log more information.
|
|
492
|
+
|
|
493
|
+
:return: `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion,
|
|
494
|
+
the response is `True`.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
async def get_neuron_for_pubkey_and_subnet():
|
|
498
|
+
uid = await meshtensor.query(
|
|
499
|
+
"MeshtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
|
|
500
|
+
)
|
|
501
|
+
if uid is None:
|
|
502
|
+
return NeuronInfo.get_null_neuron()
|
|
503
|
+
|
|
504
|
+
result = await meshtensor.neuron_for_uid(
|
|
505
|
+
uid=uid,
|
|
506
|
+
netuid=netuid,
|
|
507
|
+
block_hash=meshtensor.substrate.last_block_hash,
|
|
508
|
+
)
|
|
509
|
+
return result
|
|
510
|
+
|
|
511
|
+
print_verbose("Checking subnet status")
|
|
512
|
+
if not await meshtensor.subnet_exists(netuid):
|
|
513
|
+
print_error(
|
|
514
|
+
f"Failed: error: [bold white]subnet:{netuid}[/bold white] does not exist."
|
|
515
|
+
)
|
|
516
|
+
return False
|
|
517
|
+
|
|
518
|
+
with console.status(
|
|
519
|
+
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...",
|
|
520
|
+
spinner="aesthetic",
|
|
521
|
+
) as status:
|
|
522
|
+
neuron = await get_neuron_for_pubkey_and_subnet()
|
|
523
|
+
if not neuron.is_null:
|
|
524
|
+
print_error(
|
|
525
|
+
f"Wallet {wallet} is already registered on subnet {neuron.netuid} with uid {neuron.uid}",
|
|
526
|
+
status,
|
|
527
|
+
)
|
|
528
|
+
return True
|
|
529
|
+
|
|
530
|
+
if prompt:
|
|
531
|
+
if not confirm_action(
|
|
532
|
+
f"Continue Registration?\n"
|
|
533
|
+
f" hotkey [{COLOR_PALETTE.G.HK}]({wallet.hotkey_str})[/{COLOR_PALETTE.G.HK}]:"
|
|
534
|
+
f"\t[{COLOR_PALETTE.G.HK}]{get_hotkey_pub_ss58(wallet)}[/{COLOR_PALETTE.G.HK}]\n"
|
|
535
|
+
f" coldkey [{COLOR_PALETTE.G.CK}]({wallet.name})[/{COLOR_PALETTE.G.CK}]:"
|
|
536
|
+
f"\t[{COLOR_PALETTE.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE.G.CK}]\n"
|
|
537
|
+
f" network:\t\t[{COLOR_PALETTE.G.LINKS}]{meshtensor.network}[/{COLOR_PALETTE.G.LINKS}]\n",
|
|
538
|
+
decline=decline,
|
|
539
|
+
quiet=quiet,
|
|
540
|
+
):
|
|
541
|
+
return False
|
|
542
|
+
|
|
543
|
+
if not torch:
|
|
544
|
+
log_no_torch_error()
|
|
545
|
+
return False
|
|
546
|
+
|
|
547
|
+
# Attempt rolling registration.
|
|
548
|
+
attempts = 1
|
|
549
|
+
pow_result: Optional[POWSolution]
|
|
550
|
+
while True:
|
|
551
|
+
console.print(
|
|
552
|
+
":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts)
|
|
553
|
+
)
|
|
554
|
+
# Solve latest POW.
|
|
555
|
+
if cuda:
|
|
556
|
+
if not torch.cuda.is_available():
|
|
557
|
+
if prompt:
|
|
558
|
+
console.print("CUDA is not available.")
|
|
559
|
+
return False
|
|
560
|
+
pow_result = await create_pow(
|
|
561
|
+
meshtensor,
|
|
562
|
+
wallet,
|
|
563
|
+
netuid,
|
|
564
|
+
output_in_place,
|
|
565
|
+
cuda=cuda,
|
|
566
|
+
dev_id=dev_id,
|
|
567
|
+
tpb=tpb,
|
|
568
|
+
num_processes=num_processes,
|
|
569
|
+
update_interval=update_interval,
|
|
570
|
+
log_verbose=log_verbose,
|
|
571
|
+
)
|
|
572
|
+
else:
|
|
573
|
+
pow_result = await create_pow(
|
|
574
|
+
meshtensor,
|
|
575
|
+
wallet,
|
|
576
|
+
netuid,
|
|
577
|
+
output_in_place,
|
|
578
|
+
cuda=cuda,
|
|
579
|
+
num_processes=num_processes,
|
|
580
|
+
update_interval=update_interval,
|
|
581
|
+
log_verbose=log_verbose,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
# pow failed
|
|
585
|
+
if not pow_result:
|
|
586
|
+
# might be registered already on this subnet
|
|
587
|
+
is_registered = await is_hotkey_registered(
|
|
588
|
+
meshtensor, netuid=netuid, hotkey_ss58=get_hotkey_pub_ss58(wallet)
|
|
589
|
+
)
|
|
590
|
+
if is_registered:
|
|
591
|
+
print_success(
|
|
592
|
+
f"[dark_sea_green3]Already registered on netuid:{netuid}[/dark_sea_green3]"
|
|
593
|
+
)
|
|
594
|
+
return True
|
|
595
|
+
|
|
596
|
+
# pow successful, proceed to submit pow to chain for registration
|
|
597
|
+
else:
|
|
598
|
+
with console.status(":satellite: Submitting POW..."):
|
|
599
|
+
# check if pow result is still valid
|
|
600
|
+
while not await pow_result.is_stale(meshtensor=meshtensor):
|
|
601
|
+
call = await meshtensor.substrate.compose_call(
|
|
602
|
+
call_module="MeshtensorModule",
|
|
603
|
+
call_function="register",
|
|
604
|
+
call_params={
|
|
605
|
+
"netuid": netuid,
|
|
606
|
+
"block_number": pow_result.block_number,
|
|
607
|
+
"nonce": pow_result.nonce,
|
|
608
|
+
"work": [int(byte_) for byte_ in pow_result.seal],
|
|
609
|
+
"hotkey": get_hotkey_pub_ss58(wallet),
|
|
610
|
+
"coldkey": wallet.coldkeypub.ss58_address,
|
|
611
|
+
},
|
|
612
|
+
)
|
|
613
|
+
extrinsic = await meshtensor.substrate.create_signed_extrinsic(
|
|
614
|
+
call=call, keypair=wallet.hotkey
|
|
615
|
+
)
|
|
616
|
+
response = await meshtensor.substrate.submit_extrinsic(
|
|
617
|
+
extrinsic,
|
|
618
|
+
wait_for_inclusion=wait_for_inclusion,
|
|
619
|
+
wait_for_finalization=wait_for_finalization,
|
|
620
|
+
)
|
|
621
|
+
if not wait_for_finalization and not wait_for_inclusion:
|
|
622
|
+
success, err_msg = True, ""
|
|
623
|
+
else:
|
|
624
|
+
success = await response.is_success
|
|
625
|
+
if not success:
|
|
626
|
+
success, err_msg = (
|
|
627
|
+
False,
|
|
628
|
+
format_error_message(await response.error_message),
|
|
629
|
+
)
|
|
630
|
+
# Look error here
|
|
631
|
+
# https://github.com/opentensor/meshtensor/blob/development/pallets/meshtensor/src/errors.rs
|
|
632
|
+
|
|
633
|
+
if "HotKeyAlreadyRegisteredInSubNet" in err_msg:
|
|
634
|
+
print_success(
|
|
635
|
+
f"[dark_sea_green3]Already Registered on "
|
|
636
|
+
f"[bold]subnet:{netuid}[/bold][/dark_sea_green3]"
|
|
637
|
+
)
|
|
638
|
+
return True
|
|
639
|
+
print_error(f"Failed: {err_msg}")
|
|
640
|
+
await asyncio.sleep(0.5)
|
|
641
|
+
|
|
642
|
+
# Successful registration, final check for neuron and pubkey
|
|
643
|
+
if success:
|
|
644
|
+
console.print(":satellite: Checking Registration status...")
|
|
645
|
+
is_registered = await is_hotkey_registered(
|
|
646
|
+
meshtensor,
|
|
647
|
+
netuid=netuid,
|
|
648
|
+
hotkey_ss58=get_hotkey_pub_ss58(wallet),
|
|
649
|
+
)
|
|
650
|
+
if is_registered:
|
|
651
|
+
print_success(
|
|
652
|
+
"[dark_sea_green3]Registered[/dark_sea_green3]"
|
|
653
|
+
)
|
|
654
|
+
return True
|
|
655
|
+
else:
|
|
656
|
+
# neuron not found, try again
|
|
657
|
+
print_error("Unknown error. Neuron not found.")
|
|
658
|
+
continue
|
|
659
|
+
else:
|
|
660
|
+
# Exited loop because pow is no longer valid.
|
|
661
|
+
print_error("POW is stale.")
|
|
662
|
+
# Try again.
|
|
663
|
+
continue
|
|
664
|
+
|
|
665
|
+
if attempts < max_allowed_attempts:
|
|
666
|
+
# Failed registration, retry pow
|
|
667
|
+
attempts += 1
|
|
668
|
+
print_error(
|
|
669
|
+
":satellite: Failed registration, retrying pow ...({attempts}/{max_allowed_attempts})"
|
|
670
|
+
)
|
|
671
|
+
else:
|
|
672
|
+
# Failed to register after max attempts.
|
|
673
|
+
print_error("No more attempts.")
|
|
674
|
+
return False
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
async def burned_register_extrinsic(
|
|
678
|
+
meshtensor: "MeshtensorInterface",
|
|
679
|
+
wallet: Wallet,
|
|
680
|
+
netuid: int,
|
|
681
|
+
old_balance: Balance,
|
|
682
|
+
wait_for_inclusion: bool = True,
|
|
683
|
+
wait_for_finalization: bool = True,
|
|
684
|
+
era: Optional[int] = None,
|
|
685
|
+
proxy: Optional[str] = None,
|
|
686
|
+
) -> tuple[bool, str, Optional[str]]:
|
|
687
|
+
"""Registers the wallet to chain by recycling MESH.
|
|
688
|
+
|
|
689
|
+
:param meshtensor: The MeshtensorInterface object to use for the call, initialized
|
|
690
|
+
:param wallet: Meshtensor wallet object.
|
|
691
|
+
:param netuid: The `netuid` of the subnet to register on.
|
|
692
|
+
:param old_balance: The wallet balance prior to the registration burn.
|
|
693
|
+
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
|
694
|
+
`False` if the extrinsic fails to enter the block within the timeout.
|
|
695
|
+
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
|
696
|
+
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
|
697
|
+
:param era: the period (in blocks) for which the transaction should remain valid.
|
|
698
|
+
:param proxy: the proxy address to use for the call.
|
|
699
|
+
|
|
700
|
+
:return: (success, msg), where success is `True` if extrinsic was finalized or included in the block. If we did not
|
|
701
|
+
wait for finalization/inclusion, the response is `True`.
|
|
702
|
+
"""
|
|
703
|
+
|
|
704
|
+
if not (unlock_status := unlock_key(wallet, print_out=False)).success:
|
|
705
|
+
return False, unlock_status.message, None
|
|
706
|
+
|
|
707
|
+
with console.status(
|
|
708
|
+
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...",
|
|
709
|
+
spinner="aesthetic",
|
|
710
|
+
) as status:
|
|
711
|
+
my_uid = await meshtensor.query(
|
|
712
|
+
"MeshtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
|
|
713
|
+
)
|
|
714
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
715
|
+
|
|
716
|
+
print_verbose("Checking if already registered", status)
|
|
717
|
+
neuron = await meshtensor.neuron_for_uid(
|
|
718
|
+
uid=my_uid, netuid=netuid, block_hash=block_hash
|
|
719
|
+
)
|
|
720
|
+
if not era:
|
|
721
|
+
current_block, tempo, blocks_since_last_step = await asyncio.gather(
|
|
722
|
+
meshtensor.substrate.get_block_number(block_hash=block_hash),
|
|
723
|
+
meshtensor.get_hyperparameter(
|
|
724
|
+
"Tempo", netuid=netuid, block_hash=block_hash
|
|
725
|
+
),
|
|
726
|
+
meshtensor.query(
|
|
727
|
+
"MeshtensorModule",
|
|
728
|
+
"BlocksSinceLastStep",
|
|
729
|
+
[netuid],
|
|
730
|
+
block_hash=block_hash,
|
|
731
|
+
),
|
|
732
|
+
)
|
|
733
|
+
validity_period = tempo - blocks_since_last_step
|
|
734
|
+
era_ = {
|
|
735
|
+
"period": validity_period,
|
|
736
|
+
"current": current_block,
|
|
737
|
+
}
|
|
738
|
+
else:
|
|
739
|
+
era_ = {"period": era}
|
|
740
|
+
|
|
741
|
+
if not neuron.is_null:
|
|
742
|
+
print_success("[dark_sea_green3]Already Registered[/dark_sea_green3]:")
|
|
743
|
+
console.print(
|
|
744
|
+
f"uid: [{COLOR_PALETTE.G.NETUID_EXTRA}]{neuron.uid}[/{COLOR_PALETTE.G.NETUID_EXTRA}]\n"
|
|
745
|
+
f"netuid: [{COLOR_PALETTE.G.NETUID}]{neuron.netuid}[/{COLOR_PALETTE.G.NETUID}]\n"
|
|
746
|
+
f"hotkey: [{COLOR_PALETTE.G.HK}]{neuron.hotkey}[/{COLOR_PALETTE.G.HK}]\n"
|
|
747
|
+
f"coldkey: [{COLOR_PALETTE.G.CK}]{neuron.coldkey}[/{COLOR_PALETTE.G.CK}]"
|
|
748
|
+
)
|
|
749
|
+
return True, "Already registered", None
|
|
750
|
+
|
|
751
|
+
with console.status(
|
|
752
|
+
":satellite: Recycling MESH for Registration...", spinner="aesthetic"
|
|
753
|
+
):
|
|
754
|
+
call = await meshtensor.substrate.compose_call(
|
|
755
|
+
call_module="MeshtensorModule",
|
|
756
|
+
call_function="burned_register",
|
|
757
|
+
call_params={
|
|
758
|
+
"netuid": netuid,
|
|
759
|
+
"hotkey": get_hotkey_pub_ss58(wallet),
|
|
760
|
+
},
|
|
761
|
+
)
|
|
762
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
763
|
+
call,
|
|
764
|
+
wallet,
|
|
765
|
+
wait_for_inclusion,
|
|
766
|
+
wait_for_finalization,
|
|
767
|
+
era=era_,
|
|
768
|
+
proxy=proxy,
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
if not success:
|
|
772
|
+
print_error(f"Failed: {err_msg}")
|
|
773
|
+
await asyncio.sleep(0.5)
|
|
774
|
+
return False, err_msg, None
|
|
775
|
+
# Successful registration, final check for neuron and pubkey
|
|
776
|
+
else:
|
|
777
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
778
|
+
await print_extrinsic_id(ext_receipt)
|
|
779
|
+
with console.status(":satellite: Checking Balance...", spinner="aesthetic"):
|
|
780
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
781
|
+
new_balance, netuids_for_hotkey, my_uid = await asyncio.gather(
|
|
782
|
+
meshtensor.get_balance(
|
|
783
|
+
wallet.coldkeypub.ss58_address,
|
|
784
|
+
block_hash=block_hash,
|
|
785
|
+
reuse_block=False,
|
|
786
|
+
),
|
|
787
|
+
meshtensor.get_netuids_for_hotkey(
|
|
788
|
+
get_hotkey_pub_ss58(wallet), block_hash=block_hash
|
|
789
|
+
),
|
|
790
|
+
meshtensor.query(
|
|
791
|
+
"MeshtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
|
|
792
|
+
),
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
console.print(
|
|
796
|
+
"Balance:\n"
|
|
797
|
+
f" [blue]{old_balance}[/blue] :arrow_right: "
|
|
798
|
+
f"[{COLOR_PALETTE.S.STAKE_AMOUNT}]{new_balance}[/{COLOR_PALETTE.S.STAKE_AMOUNT}]"
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
if len(netuids_for_hotkey) > 0:
|
|
802
|
+
print_success(f"Registered on netuid {netuid} with UID {my_uid}")
|
|
803
|
+
return True, f"Registered on {netuid} with UID {my_uid}", ext_id
|
|
804
|
+
else:
|
|
805
|
+
# neuron not found, try again
|
|
806
|
+
print_error("Unknown error. Neuron not found.")
|
|
807
|
+
return False, "Unknown error. Neuron not found.", ext_id
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
async def run_faucet_extrinsic(
|
|
811
|
+
meshtensor: "MeshtensorInterface",
|
|
812
|
+
wallet: Wallet,
|
|
813
|
+
wait_for_inclusion: bool = False,
|
|
814
|
+
wait_for_finalization: bool = True,
|
|
815
|
+
prompt: bool = False,
|
|
816
|
+
decline: bool = False,
|
|
817
|
+
quiet: bool = False,
|
|
818
|
+
max_allowed_attempts: int = 3,
|
|
819
|
+
output_in_place: bool = True,
|
|
820
|
+
cuda: bool = False,
|
|
821
|
+
dev_id: int = 0,
|
|
822
|
+
tpb: int = 256,
|
|
823
|
+
num_processes: Optional[int] = None,
|
|
824
|
+
update_interval: Optional[int] = None,
|
|
825
|
+
log_verbose: bool = True,
|
|
826
|
+
max_successes: int = 3,
|
|
827
|
+
) -> tuple[bool, str]:
|
|
828
|
+
r"""Runs a continual POW to get a faucet of MESH on the test net.
|
|
829
|
+
|
|
830
|
+
:param meshtensor: The meshtensor interface object used to run the extrinsic
|
|
831
|
+
:param wallet: Meshtensor wallet object.
|
|
832
|
+
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
|
833
|
+
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`,
|
|
834
|
+
or returns `False` if the extrinsic fails to enter the block within the timeout.
|
|
835
|
+
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
|
836
|
+
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
|
837
|
+
:param max_allowed_attempts: Maximum number of attempts to register the wallet.
|
|
838
|
+
:param output_in_place: Whether to output logging data as the process runs.
|
|
839
|
+
:param cuda: If `True`, the wallet should be registered using CUDA device(s).
|
|
840
|
+
:param dev_id: The CUDA device id to use
|
|
841
|
+
:param tpb: The number of threads per block (CUDA).
|
|
842
|
+
:param num_processes: The number of processes to use to register.
|
|
843
|
+
:param update_interval: The number of nonces to solve between updates.
|
|
844
|
+
:param log_verbose: If `True`, the registration process will log more information.
|
|
845
|
+
:param max_successes: The maximum number of successful faucet runs for the wallet.
|
|
846
|
+
|
|
847
|
+
:return: `True` if extrinsic was finalized or included in the block. If we did not wait for
|
|
848
|
+
finalization/inclusion, the response is also `True`
|
|
849
|
+
"""
|
|
850
|
+
if prompt:
|
|
851
|
+
if not confirm_action(
|
|
852
|
+
"Run Faucet?\n"
|
|
853
|
+
f" wallet name: [bold white]{wallet.name}[/bold white]\n"
|
|
854
|
+
f" coldkey: [bold white]{wallet.coldkeypub.ss58_address}[/bold white]\n"
|
|
855
|
+
f" network: [bold white]{meshtensor}[/bold white]",
|
|
856
|
+
decline=decline,
|
|
857
|
+
quiet=quiet,
|
|
858
|
+
):
|
|
859
|
+
return False, ""
|
|
860
|
+
|
|
861
|
+
if not torch:
|
|
862
|
+
log_no_torch_error()
|
|
863
|
+
return False, "Requires torch"
|
|
864
|
+
|
|
865
|
+
# Unlock coldkey
|
|
866
|
+
if not (unlock_status := unlock_key(wallet, print_out=False)).success:
|
|
867
|
+
return False, unlock_status.message
|
|
868
|
+
|
|
869
|
+
# Get previous balance.
|
|
870
|
+
old_balance = await meshtensor.get_balance(wallet.coldkeypub.ss58_address)
|
|
871
|
+
|
|
872
|
+
# Attempt rolling registration.
|
|
873
|
+
attempts = 1
|
|
874
|
+
successes = 1
|
|
875
|
+
pow_result: Optional[POWSolution]
|
|
876
|
+
while True:
|
|
877
|
+
try:
|
|
878
|
+
account_nonce = await meshtensor.substrate.get_account_nonce(
|
|
879
|
+
wallet.coldkey.ss58_address
|
|
880
|
+
)
|
|
881
|
+
pow_result = None
|
|
882
|
+
while pow_result is None or await pow_result.is_stale(meshtensor=meshtensor):
|
|
883
|
+
# Solve latest POW.
|
|
884
|
+
if cuda:
|
|
885
|
+
if not torch.cuda.is_available():
|
|
886
|
+
if prompt:
|
|
887
|
+
print_error("CUDA is not available.")
|
|
888
|
+
return False, "CUDA is not available."
|
|
889
|
+
pow_result = await create_pow(
|
|
890
|
+
meshtensor,
|
|
891
|
+
wallet,
|
|
892
|
+
-1,
|
|
893
|
+
output_in_place,
|
|
894
|
+
cuda=cuda,
|
|
895
|
+
dev_id=dev_id,
|
|
896
|
+
tpb=tpb,
|
|
897
|
+
num_processes=num_processes,
|
|
898
|
+
update_interval=update_interval,
|
|
899
|
+
log_verbose=log_verbose,
|
|
900
|
+
)
|
|
901
|
+
else:
|
|
902
|
+
pow_result = await create_pow(
|
|
903
|
+
meshtensor,
|
|
904
|
+
wallet,
|
|
905
|
+
-1,
|
|
906
|
+
output_in_place,
|
|
907
|
+
cuda=cuda,
|
|
908
|
+
num_processes=num_processes,
|
|
909
|
+
update_interval=update_interval,
|
|
910
|
+
log_verbose=log_verbose,
|
|
911
|
+
)
|
|
912
|
+
call = await meshtensor.substrate.compose_call(
|
|
913
|
+
call_module="MeshtensorModule",
|
|
914
|
+
call_function="faucet",
|
|
915
|
+
call_params={
|
|
916
|
+
"block_number": pow_result.block_number,
|
|
917
|
+
"nonce": pow_result.nonce,
|
|
918
|
+
"work": [int(byte_) for byte_ in pow_result.seal],
|
|
919
|
+
},
|
|
920
|
+
)
|
|
921
|
+
extrinsic = await meshtensor.substrate.create_signed_extrinsic(
|
|
922
|
+
call=call, keypair=wallet.coldkey, nonce=account_nonce
|
|
923
|
+
)
|
|
924
|
+
response = await meshtensor.substrate.submit_extrinsic(
|
|
925
|
+
extrinsic,
|
|
926
|
+
wait_for_inclusion=wait_for_inclusion,
|
|
927
|
+
wait_for_finalization=wait_for_finalization,
|
|
928
|
+
)
|
|
929
|
+
|
|
930
|
+
# process if registration successful, try again if pow is still valid
|
|
931
|
+
if not await response.is_success:
|
|
932
|
+
print_error(
|
|
933
|
+
f"Failed: {format_error_message(await response.error_message)}"
|
|
934
|
+
)
|
|
935
|
+
if attempts == max_allowed_attempts:
|
|
936
|
+
raise MaxAttemptsException
|
|
937
|
+
attempts += 1
|
|
938
|
+
# Wait a bit before trying again
|
|
939
|
+
time.sleep(1)
|
|
940
|
+
|
|
941
|
+
# Successful registration
|
|
942
|
+
else:
|
|
943
|
+
new_balance = await meshtensor.get_balance(
|
|
944
|
+
wallet.coldkeypub.ss58_address
|
|
945
|
+
)
|
|
946
|
+
console.print(
|
|
947
|
+
f"Balance: [blue]{old_balance}[/blue] :arrow_right:"
|
|
948
|
+
f" [green]{new_balance}[/green]"
|
|
949
|
+
)
|
|
950
|
+
old_balance = new_balance
|
|
951
|
+
|
|
952
|
+
if successes == max_successes:
|
|
953
|
+
raise MaxSuccessException
|
|
954
|
+
|
|
955
|
+
attempts = 1 # Reset attempts on success
|
|
956
|
+
successes += 1
|
|
957
|
+
|
|
958
|
+
except KeyboardInterrupt:
|
|
959
|
+
return True, "Done"
|
|
960
|
+
|
|
961
|
+
except MaxSuccessException:
|
|
962
|
+
return True, f"Max successes reached: {3}"
|
|
963
|
+
|
|
964
|
+
except MaxAttemptsException:
|
|
965
|
+
return False, f"Max attempts reached: {max_allowed_attempts}"
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
async def _check_for_newest_block_and_update(
|
|
969
|
+
meshtensor: "MeshtensorInterface",
|
|
970
|
+
netuid: int,
|
|
971
|
+
old_block_number: int,
|
|
972
|
+
hotkey_bytes: bytes,
|
|
973
|
+
curr_diff: Array,
|
|
974
|
+
curr_block: Array,
|
|
975
|
+
curr_block_num: Value,
|
|
976
|
+
update_curr_block: typing.Callable,
|
|
977
|
+
check_block: Lock,
|
|
978
|
+
solvers: list[_Solver],
|
|
979
|
+
curr_stats: RegistrationStatistics,
|
|
980
|
+
) -> int:
|
|
981
|
+
"""
|
|
982
|
+
Checks for a new block and updates the current block information if a new block is found.
|
|
983
|
+
|
|
984
|
+
:param meshtensor: The meshtensor object to use for getting the current block.
|
|
985
|
+
:param netuid: The netuid to use for retrieving the difficulty.
|
|
986
|
+
:param old_block_number: The old block number to check against.
|
|
987
|
+
:param hotkey_bytes: The bytes of the hotkey's pubkey.
|
|
988
|
+
:param curr_diff: The current difficulty as a multiprocessing array.
|
|
989
|
+
:param curr_block: Where the current block is stored as a multiprocessing array.
|
|
990
|
+
:param curr_block_num: Where the current block number is stored as a multiprocessing value.
|
|
991
|
+
:param update_curr_block: A function that updates the current block.
|
|
992
|
+
:param check_block: A mp lock that is used to check for a new block.
|
|
993
|
+
:param solvers: A list of solvers to update the current block for.
|
|
994
|
+
:param curr_stats: The current registration statistics to update.
|
|
995
|
+
|
|
996
|
+
:return: The current block number.
|
|
997
|
+
"""
|
|
998
|
+
block_number = await meshtensor.substrate.get_block_number(None)
|
|
999
|
+
if block_number != old_block_number:
|
|
1000
|
+
old_block_number = block_number
|
|
1001
|
+
# update block information
|
|
1002
|
+
block_number, difficulty, block_hash = await _get_block_with_retry(
|
|
1003
|
+
meshtensor=meshtensor, netuid=netuid
|
|
1004
|
+
)
|
|
1005
|
+
block_bytes = hex_to_bytes(block_hash)
|
|
1006
|
+
|
|
1007
|
+
update_curr_block(
|
|
1008
|
+
curr_diff,
|
|
1009
|
+
curr_block,
|
|
1010
|
+
curr_block_num,
|
|
1011
|
+
block_number,
|
|
1012
|
+
block_bytes,
|
|
1013
|
+
difficulty,
|
|
1014
|
+
hotkey_bytes,
|
|
1015
|
+
check_block,
|
|
1016
|
+
)
|
|
1017
|
+
# Set new block events for each solver
|
|
1018
|
+
|
|
1019
|
+
for worker in solvers:
|
|
1020
|
+
worker.new_block_event.set()
|
|
1021
|
+
|
|
1022
|
+
# update stats
|
|
1023
|
+
curr_stats.block_number = block_number
|
|
1024
|
+
curr_stats.block_hash = block_hash
|
|
1025
|
+
curr_stats.difficulty = difficulty
|
|
1026
|
+
|
|
1027
|
+
return old_block_number
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+
async def _block_solver(
|
|
1031
|
+
meshtensor: "MeshtensorInterface",
|
|
1032
|
+
wallet: Wallet,
|
|
1033
|
+
num_processes: int,
|
|
1034
|
+
netuid: int,
|
|
1035
|
+
dev_id: list[int],
|
|
1036
|
+
tpb: int,
|
|
1037
|
+
update_interval: int,
|
|
1038
|
+
curr_block,
|
|
1039
|
+
curr_block_num,
|
|
1040
|
+
curr_diff,
|
|
1041
|
+
n_samples,
|
|
1042
|
+
alpha_,
|
|
1043
|
+
output_in_place,
|
|
1044
|
+
log_verbose,
|
|
1045
|
+
cuda: bool,
|
|
1046
|
+
):
|
|
1047
|
+
"""
|
|
1048
|
+
Shared code used by the Solvers to solve the POW solution
|
|
1049
|
+
"""
|
|
1050
|
+
limit = int(math.pow(2, 256)) - 1
|
|
1051
|
+
|
|
1052
|
+
# Establish communication queues
|
|
1053
|
+
# See the _Solver class for more information on the queues.
|
|
1054
|
+
stop_event = Event()
|
|
1055
|
+
stop_event.clear()
|
|
1056
|
+
|
|
1057
|
+
solution_queue = Queue()
|
|
1058
|
+
if cuda:
|
|
1059
|
+
num_processes = len(dev_id)
|
|
1060
|
+
|
|
1061
|
+
finished_queues = [Queue() for _ in range(num_processes)]
|
|
1062
|
+
check_block = Lock()
|
|
1063
|
+
|
|
1064
|
+
hotkey_bytes = (
|
|
1065
|
+
wallet.coldkeypub.public_key if netuid == -1 else wallet.hotkey.public_key
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
if cuda:
|
|
1069
|
+
# Create a worker per CUDA device
|
|
1070
|
+
solvers = [
|
|
1071
|
+
_CUDASolver(
|
|
1072
|
+
i,
|
|
1073
|
+
num_processes,
|
|
1074
|
+
update_interval,
|
|
1075
|
+
finished_queues[i],
|
|
1076
|
+
solution_queue,
|
|
1077
|
+
stop_event,
|
|
1078
|
+
curr_block,
|
|
1079
|
+
curr_block_num,
|
|
1080
|
+
curr_diff,
|
|
1081
|
+
check_block,
|
|
1082
|
+
limit,
|
|
1083
|
+
dev_id[i],
|
|
1084
|
+
tpb,
|
|
1085
|
+
)
|
|
1086
|
+
for i in range(num_processes)
|
|
1087
|
+
]
|
|
1088
|
+
else:
|
|
1089
|
+
# Start consumers
|
|
1090
|
+
solvers = [
|
|
1091
|
+
_Solver(
|
|
1092
|
+
i,
|
|
1093
|
+
num_processes,
|
|
1094
|
+
update_interval,
|
|
1095
|
+
finished_queues[i],
|
|
1096
|
+
solution_queue,
|
|
1097
|
+
stop_event,
|
|
1098
|
+
curr_block,
|
|
1099
|
+
curr_block_num,
|
|
1100
|
+
curr_diff,
|
|
1101
|
+
check_block,
|
|
1102
|
+
limit,
|
|
1103
|
+
)
|
|
1104
|
+
for i in range(num_processes)
|
|
1105
|
+
]
|
|
1106
|
+
|
|
1107
|
+
# Get first block
|
|
1108
|
+
block_number, difficulty, block_hash = await _get_block_with_retry(
|
|
1109
|
+
meshtensor=meshtensor, netuid=netuid
|
|
1110
|
+
)
|
|
1111
|
+
|
|
1112
|
+
block_bytes = hex_to_bytes(block_hash)
|
|
1113
|
+
old_block_number = block_number
|
|
1114
|
+
# Set to current block
|
|
1115
|
+
_update_curr_block(
|
|
1116
|
+
curr_diff,
|
|
1117
|
+
curr_block,
|
|
1118
|
+
curr_block_num,
|
|
1119
|
+
block_number,
|
|
1120
|
+
block_bytes,
|
|
1121
|
+
difficulty,
|
|
1122
|
+
hotkey_bytes,
|
|
1123
|
+
check_block,
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
# Set new block events for each solver to start at the initial block
|
|
1127
|
+
for worker in solvers:
|
|
1128
|
+
worker.new_block_event.set()
|
|
1129
|
+
|
|
1130
|
+
for worker in solvers:
|
|
1131
|
+
worker.start() # start the solver processes
|
|
1132
|
+
|
|
1133
|
+
start_time = time.time() # time that the registration started
|
|
1134
|
+
time_last = start_time # time that the last work blocks completed
|
|
1135
|
+
|
|
1136
|
+
curr_stats = RegistrationStatistics(
|
|
1137
|
+
time_spent_total=0.0,
|
|
1138
|
+
time_average=0.0,
|
|
1139
|
+
rounds_total=0,
|
|
1140
|
+
time_spent=0.0,
|
|
1141
|
+
hash_rate_perpetual=0.0,
|
|
1142
|
+
hash_rate=0.0,
|
|
1143
|
+
difficulty=difficulty,
|
|
1144
|
+
block_number=block_number,
|
|
1145
|
+
block_hash=block_hash,
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
start_time_perpetual = time.time()
|
|
1149
|
+
|
|
1150
|
+
logger = RegistrationStatisticsLogger(console, output_in_place)
|
|
1151
|
+
logger.start()
|
|
1152
|
+
|
|
1153
|
+
solution = None
|
|
1154
|
+
|
|
1155
|
+
hash_rates = [0] * n_samples # The last n true hash_rates
|
|
1156
|
+
weights = [alpha_**i for i in range(n_samples)] # weights decay by alpha
|
|
1157
|
+
|
|
1158
|
+
timeout = 0.15 if cuda else 0.15
|
|
1159
|
+
while netuid == -1 or not await is_hotkey_registered(
|
|
1160
|
+
meshtensor, netuid, get_hotkey_pub_ss58(wallet)
|
|
1161
|
+
):
|
|
1162
|
+
# Wait until a solver finds a solution
|
|
1163
|
+
try:
|
|
1164
|
+
solution = solution_queue.get(block=True, timeout=timeout)
|
|
1165
|
+
if solution is not None:
|
|
1166
|
+
break
|
|
1167
|
+
except Empty:
|
|
1168
|
+
# No solution found, try again
|
|
1169
|
+
pass
|
|
1170
|
+
|
|
1171
|
+
# check for new block
|
|
1172
|
+
old_block_number = await _check_for_newest_block_and_update(
|
|
1173
|
+
meshtensor=meshtensor,
|
|
1174
|
+
netuid=netuid,
|
|
1175
|
+
hotkey_bytes=hotkey_bytes,
|
|
1176
|
+
old_block_number=old_block_number,
|
|
1177
|
+
curr_diff=curr_diff,
|
|
1178
|
+
curr_block=curr_block,
|
|
1179
|
+
curr_block_num=curr_block_num,
|
|
1180
|
+
curr_stats=curr_stats,
|
|
1181
|
+
update_curr_block=_update_curr_block,
|
|
1182
|
+
check_block=check_block,
|
|
1183
|
+
solvers=solvers,
|
|
1184
|
+
)
|
|
1185
|
+
|
|
1186
|
+
num_time = 0
|
|
1187
|
+
for finished_queue in finished_queues:
|
|
1188
|
+
try:
|
|
1189
|
+
finished_queue.get(timeout=0.1)
|
|
1190
|
+
num_time += 1
|
|
1191
|
+
|
|
1192
|
+
except Empty:
|
|
1193
|
+
continue
|
|
1194
|
+
|
|
1195
|
+
time_now = time.time() # get current time
|
|
1196
|
+
time_since_last = time_now - time_last # get time since last work block(s)
|
|
1197
|
+
if num_time > 0 and time_since_last > 0.0:
|
|
1198
|
+
# create EWMA of the hash_rate to make measure more robust
|
|
1199
|
+
|
|
1200
|
+
if cuda:
|
|
1201
|
+
hash_rate_ = (num_time * tpb * update_interval) / time_since_last
|
|
1202
|
+
else:
|
|
1203
|
+
hash_rate_ = (num_time * update_interval) / time_since_last
|
|
1204
|
+
hash_rates.append(hash_rate_)
|
|
1205
|
+
hash_rates.pop(0) # remove the 0th data point
|
|
1206
|
+
curr_stats.hash_rate = sum(
|
|
1207
|
+
[hash_rates[i] * weights[i] for i in range(n_samples)]
|
|
1208
|
+
) / (sum(weights))
|
|
1209
|
+
|
|
1210
|
+
# update time last to now
|
|
1211
|
+
time_last = time_now
|
|
1212
|
+
|
|
1213
|
+
curr_stats.time_average = (
|
|
1214
|
+
curr_stats.time_average * curr_stats.rounds_total
|
|
1215
|
+
+ curr_stats.time_spent
|
|
1216
|
+
) / (curr_stats.rounds_total + num_time)
|
|
1217
|
+
curr_stats.rounds_total += num_time
|
|
1218
|
+
|
|
1219
|
+
# Update stats
|
|
1220
|
+
curr_stats.time_spent = time_since_last
|
|
1221
|
+
new_time_spent_total = time_now - start_time_perpetual
|
|
1222
|
+
if cuda:
|
|
1223
|
+
curr_stats.hash_rate_perpetual = (
|
|
1224
|
+
curr_stats.rounds_total * (tpb * update_interval)
|
|
1225
|
+
) / new_time_spent_total
|
|
1226
|
+
else:
|
|
1227
|
+
curr_stats.hash_rate_perpetual = (
|
|
1228
|
+
curr_stats.rounds_total * update_interval
|
|
1229
|
+
) / new_time_spent_total
|
|
1230
|
+
curr_stats.time_spent_total = new_time_spent_total
|
|
1231
|
+
|
|
1232
|
+
# Update the logger
|
|
1233
|
+
logger.update(curr_stats, verbose=log_verbose)
|
|
1234
|
+
|
|
1235
|
+
# exited while, solution contains the nonce or wallet is registered
|
|
1236
|
+
stop_event.set() # stop all other processes
|
|
1237
|
+
logger.stop()
|
|
1238
|
+
|
|
1239
|
+
# terminate and wait for all solvers to exit
|
|
1240
|
+
_terminate_workers_and_wait_for_exit(solvers)
|
|
1241
|
+
|
|
1242
|
+
return solution
|
|
1243
|
+
|
|
1244
|
+
|
|
1245
|
+
async def _solve_for_difficulty_fast_cuda(
|
|
1246
|
+
meshtensor: "MeshtensorInterface",
|
|
1247
|
+
wallet: Wallet,
|
|
1248
|
+
netuid: int,
|
|
1249
|
+
output_in_place: bool = True,
|
|
1250
|
+
update_interval: int = 50_000,
|
|
1251
|
+
tpb: int = 512,
|
|
1252
|
+
dev_id: typing.Union[list[int], int] = 0,
|
|
1253
|
+
n_samples: int = 10,
|
|
1254
|
+
alpha_: float = 0.80,
|
|
1255
|
+
log_verbose: bool = False,
|
|
1256
|
+
) -> Optional[POWSolution]:
|
|
1257
|
+
"""
|
|
1258
|
+
Solves the registration fast using CUDA
|
|
1259
|
+
|
|
1260
|
+
:param meshtensor: The meshtensor node to grab blocks
|
|
1261
|
+
:param wallet: The wallet to register
|
|
1262
|
+
:param netuid: The netuid of the subnet to register to.
|
|
1263
|
+
:param output_in_place: If true, prints the output in place, otherwise prints to new lines
|
|
1264
|
+
:param update_interval: The number of nonces to try before checking for more blocks
|
|
1265
|
+
:param tpb: The number of threads per block. CUDA param that should match the GPU capability
|
|
1266
|
+
:param dev_id: The CUDA device IDs to execute the registration on, either a single device or a list of devices
|
|
1267
|
+
:param n_samples: The number of samples of the hash_rate to keep for the EWMA
|
|
1268
|
+
:param alpha_: The alpha for the EWMA for the hash_rate calculation
|
|
1269
|
+
:param log_verbose: If true, prints more verbose logging of the registration metrics.
|
|
1270
|
+
|
|
1271
|
+
Note: The hash rate is calculated as an exponentially weighted moving average in order to make the measure more
|
|
1272
|
+
robust.
|
|
1273
|
+
"""
|
|
1274
|
+
if isinstance(dev_id, int):
|
|
1275
|
+
dev_id = [dev_id]
|
|
1276
|
+
elif dev_id is None:
|
|
1277
|
+
dev_id = [0]
|
|
1278
|
+
|
|
1279
|
+
if update_interval is None:
|
|
1280
|
+
update_interval = 50_000
|
|
1281
|
+
|
|
1282
|
+
if not torch.cuda.is_available():
|
|
1283
|
+
raise Exception("CUDA not available")
|
|
1284
|
+
|
|
1285
|
+
# Set mp start to use spawn so CUDA doesn't complain
|
|
1286
|
+
with _UsingSpawnStartMethod(force=True):
|
|
1287
|
+
curr_block, curr_block_num, curr_diff = _CUDASolver.create_shared_memory()
|
|
1288
|
+
|
|
1289
|
+
solution = await _block_solver(
|
|
1290
|
+
meshtensor=meshtensor,
|
|
1291
|
+
wallet=wallet,
|
|
1292
|
+
num_processes=None,
|
|
1293
|
+
netuid=netuid,
|
|
1294
|
+
dev_id=dev_id,
|
|
1295
|
+
tpb=tpb,
|
|
1296
|
+
update_interval=update_interval,
|
|
1297
|
+
curr_block=curr_block,
|
|
1298
|
+
curr_block_num=curr_block_num,
|
|
1299
|
+
curr_diff=curr_diff,
|
|
1300
|
+
n_samples=n_samples,
|
|
1301
|
+
alpha_=alpha_,
|
|
1302
|
+
output_in_place=output_in_place,
|
|
1303
|
+
log_verbose=log_verbose,
|
|
1304
|
+
cuda=True,
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
return solution
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
async def _solve_for_difficulty_fast(
|
|
1311
|
+
meshtensor,
|
|
1312
|
+
wallet: Wallet,
|
|
1313
|
+
netuid: int,
|
|
1314
|
+
output_in_place: bool = True,
|
|
1315
|
+
num_processes: Optional[int] = None,
|
|
1316
|
+
update_interval: Optional[int] = None,
|
|
1317
|
+
n_samples: int = 10,
|
|
1318
|
+
alpha_: float = 0.80,
|
|
1319
|
+
log_verbose: bool = False,
|
|
1320
|
+
) -> Optional[POWSolution]:
|
|
1321
|
+
"""
|
|
1322
|
+
Solves the POW for registration using multiprocessing.
|
|
1323
|
+
|
|
1324
|
+
:param meshtensor: Meshtensor to connect to for block information and to submit.
|
|
1325
|
+
:param wallet: wallet to use for registration.
|
|
1326
|
+
:param netuid: The netuid of the subnet to register to.
|
|
1327
|
+
:param output_in_place: If true, prints the status in place. Otherwise, prints the status on a new line.
|
|
1328
|
+
:param num_processes: Number of processes to use.
|
|
1329
|
+
:param update_interval: Number of nonces to solve before updating block information.
|
|
1330
|
+
:param n_samples: The number of samples of the hash_rate to keep for the EWMA
|
|
1331
|
+
:param alpha_: The alpha for the EWMA for the hash_rate calculation
|
|
1332
|
+
:param log_verbose: If true, prints more verbose logging of the registration metrics.
|
|
1333
|
+
|
|
1334
|
+
Notes:
|
|
1335
|
+
|
|
1336
|
+
- The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust.
|
|
1337
|
+
- We can also modify the update interval to do smaller blocks of work, while still updating the block information
|
|
1338
|
+
after a different number of nonces, to increase the transparency of the process while still keeping the speed.
|
|
1339
|
+
"""
|
|
1340
|
+
if not num_processes:
|
|
1341
|
+
# get the number of allowed processes for this process
|
|
1342
|
+
num_processes = min(1, get_cpu_count())
|
|
1343
|
+
|
|
1344
|
+
if update_interval is None:
|
|
1345
|
+
update_interval = 50_000
|
|
1346
|
+
|
|
1347
|
+
curr_block, curr_block_num, curr_diff = _Solver.create_shared_memory()
|
|
1348
|
+
|
|
1349
|
+
solution = await _block_solver(
|
|
1350
|
+
meshtensor=meshtensor,
|
|
1351
|
+
wallet=wallet,
|
|
1352
|
+
num_processes=num_processes,
|
|
1353
|
+
netuid=netuid,
|
|
1354
|
+
dev_id=None,
|
|
1355
|
+
tpb=None,
|
|
1356
|
+
update_interval=update_interval,
|
|
1357
|
+
curr_block=curr_block,
|
|
1358
|
+
curr_block_num=curr_block_num,
|
|
1359
|
+
curr_diff=curr_diff,
|
|
1360
|
+
n_samples=n_samples,
|
|
1361
|
+
alpha_=alpha_,
|
|
1362
|
+
output_in_place=output_in_place,
|
|
1363
|
+
log_verbose=log_verbose,
|
|
1364
|
+
cuda=False,
|
|
1365
|
+
)
|
|
1366
|
+
|
|
1367
|
+
return solution
|
|
1368
|
+
|
|
1369
|
+
|
|
1370
|
+
def _terminate_workers_and_wait_for_exit(
|
|
1371
|
+
workers: list[typing.Union[Process, Queue_Type]],
|
|
1372
|
+
) -> None:
|
|
1373
|
+
for worker in workers:
|
|
1374
|
+
if isinstance(worker, Queue_Type):
|
|
1375
|
+
worker.join_thread()
|
|
1376
|
+
else:
|
|
1377
|
+
try:
|
|
1378
|
+
worker.join(3.0)
|
|
1379
|
+
except subprocess.TimeoutExpired:
|
|
1380
|
+
worker.terminate()
|
|
1381
|
+
try:
|
|
1382
|
+
worker.close()
|
|
1383
|
+
except ValueError:
|
|
1384
|
+
worker.terminate()
|
|
1385
|
+
|
|
1386
|
+
|
|
1387
|
+
async def _get_block_with_retry(
|
|
1388
|
+
meshtensor: "MeshtensorInterface", netuid: int
|
|
1389
|
+
) -> tuple[int, int, str]:
|
|
1390
|
+
"""
|
|
1391
|
+
Gets the current block number, difficulty, and block hash from the substrate node.
|
|
1392
|
+
|
|
1393
|
+
:param meshtensor: The meshtensor object to use to get the block number, difficulty, and block hash.
|
|
1394
|
+
:param netuid: The netuid of the network to get the block number, difficulty, and block hash from.
|
|
1395
|
+
|
|
1396
|
+
:return: The current block number, difficulty of the subnet, block hash
|
|
1397
|
+
|
|
1398
|
+
:raises Exception: If the block hash is None.
|
|
1399
|
+
:raises ValueError: If the difficulty is None.
|
|
1400
|
+
"""
|
|
1401
|
+
block = await meshtensor.substrate.get_block()
|
|
1402
|
+
block_hash = block["header"]["hash"]
|
|
1403
|
+
block_number = block["header"]["number"]
|
|
1404
|
+
try:
|
|
1405
|
+
difficulty = (
|
|
1406
|
+
1_000_000
|
|
1407
|
+
if netuid == -1
|
|
1408
|
+
else int(
|
|
1409
|
+
await meshtensor.get_hyperparameter(
|
|
1410
|
+
param_name="Difficulty", netuid=netuid, block_hash=block_hash
|
|
1411
|
+
)
|
|
1412
|
+
)
|
|
1413
|
+
)
|
|
1414
|
+
except TypeError:
|
|
1415
|
+
raise ValueError("Chain error. Difficulty is None")
|
|
1416
|
+
except SubstrateRequestException:
|
|
1417
|
+
raise Exception(
|
|
1418
|
+
"Network error. Could not connect to substrate to get block hash"
|
|
1419
|
+
)
|
|
1420
|
+
return block_number, difficulty, block_hash
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
def _registration_diff_unpack(packed_diff: Array) -> int:
|
|
1424
|
+
"""Unpacks the packed two 32-bit integers into one 64-bit integer. Little endian."""
|
|
1425
|
+
return int(packed_diff[0] << 32 | packed_diff[1])
|
|
1426
|
+
|
|
1427
|
+
|
|
1428
|
+
def _registration_diff_pack(diff: int, packed_diff: Array):
|
|
1429
|
+
"""Packs the difficulty into two 32-bit integers. Little endian."""
|
|
1430
|
+
packed_diff[0] = diff >> 32
|
|
1431
|
+
packed_diff[1] = diff & 0xFFFFFFFF # low 32 bits
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
class _UsingSpawnStartMethod:
|
|
1435
|
+
def __init__(self, force: bool = False):
|
|
1436
|
+
self._old_start_method = None
|
|
1437
|
+
self._force = force
|
|
1438
|
+
|
|
1439
|
+
def __enter__(self):
|
|
1440
|
+
self._old_start_method = mp.get_start_method(allow_none=True)
|
|
1441
|
+
if self._old_start_method is None:
|
|
1442
|
+
self._old_start_method = "spawn" # default to spawn
|
|
1443
|
+
|
|
1444
|
+
mp.set_start_method("spawn", force=self._force)
|
|
1445
|
+
|
|
1446
|
+
def __exit__(self, *args):
|
|
1447
|
+
# restore the old start method
|
|
1448
|
+
mp.set_start_method(self._old_start_method, force=True)
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
async def create_pow(
|
|
1452
|
+
meshtensor: "MeshtensorInterface",
|
|
1453
|
+
wallet: Wallet,
|
|
1454
|
+
netuid: int,
|
|
1455
|
+
output_in_place: bool = True,
|
|
1456
|
+
cuda: bool = False,
|
|
1457
|
+
dev_id: typing.Union[list[int], int] = 0,
|
|
1458
|
+
tpb: int = 256,
|
|
1459
|
+
num_processes: int = None,
|
|
1460
|
+
update_interval: int = None,
|
|
1461
|
+
log_verbose: bool = False,
|
|
1462
|
+
) -> Optional[dict[str, typing.Any]]:
|
|
1463
|
+
"""
|
|
1464
|
+
Creates a proof of work for the given meshtensor and wallet.
|
|
1465
|
+
|
|
1466
|
+
:param meshtensor: The meshtensor to create a proof of work for.
|
|
1467
|
+
:param wallet: The wallet to create a proof of work for.
|
|
1468
|
+
:param netuid: The netuid for the subnet to create a proof of work for.
|
|
1469
|
+
:param output_in_place: If true, prints the progress of the proof of work to the console
|
|
1470
|
+
in-place. Meaning the progress is printed on the same lines.
|
|
1471
|
+
:param cuda: If true, uses CUDA to solve the proof of work.
|
|
1472
|
+
:param dev_id: The CUDA device id(s) to use. If cuda is true and dev_id is a list,
|
|
1473
|
+
then multiple CUDA devices will be used to solve the proof of work.
|
|
1474
|
+
:param tpb: The number of threads per block to use when solving the proof of work. Should be a multiple of 32.
|
|
1475
|
+
:param num_processes: The number of processes to use when solving the proof of work.
|
|
1476
|
+
If None, then the number of processes is equal to the number of CPU cores.
|
|
1477
|
+
:param update_interval: The number of nonces to run before checking for a new block.
|
|
1478
|
+
:param log_verbose: If true, prints the progress of the proof of work more verbosely.
|
|
1479
|
+
|
|
1480
|
+
:return: The proof of work solution or None if the wallet is already registered or there is a different error.
|
|
1481
|
+
|
|
1482
|
+
:raises ValueError: If the subnet does not exist.
|
|
1483
|
+
"""
|
|
1484
|
+
if netuid != -1:
|
|
1485
|
+
if not await meshtensor.subnet_exists(netuid=netuid):
|
|
1486
|
+
raise ValueError(f"Subnet {netuid} does not exist")
|
|
1487
|
+
|
|
1488
|
+
if cuda:
|
|
1489
|
+
solution: Optional[POWSolution] = await _solve_for_difficulty_fast_cuda(
|
|
1490
|
+
meshtensor,
|
|
1491
|
+
wallet,
|
|
1492
|
+
netuid=netuid,
|
|
1493
|
+
output_in_place=output_in_place,
|
|
1494
|
+
dev_id=dev_id,
|
|
1495
|
+
tpb=tpb,
|
|
1496
|
+
update_interval=update_interval,
|
|
1497
|
+
log_verbose=log_verbose,
|
|
1498
|
+
)
|
|
1499
|
+
else:
|
|
1500
|
+
solution: Optional[POWSolution] = await _solve_for_difficulty_fast(
|
|
1501
|
+
meshtensor,
|
|
1502
|
+
wallet,
|
|
1503
|
+
netuid=netuid,
|
|
1504
|
+
output_in_place=output_in_place,
|
|
1505
|
+
num_processes=num_processes,
|
|
1506
|
+
update_interval=update_interval,
|
|
1507
|
+
log_verbose=log_verbose,
|
|
1508
|
+
)
|
|
1509
|
+
|
|
1510
|
+
return solution
|
|
1511
|
+
|
|
1512
|
+
|
|
1513
|
+
def _solve_for_nonce_block_cuda(
|
|
1514
|
+
nonce_start: int,
|
|
1515
|
+
update_interval: int,
|
|
1516
|
+
block_and_hotkey_hash_bytes: bytes,
|
|
1517
|
+
difficulty: int,
|
|
1518
|
+
limit: int,
|
|
1519
|
+
block_number: int,
|
|
1520
|
+
dev_id: int,
|
|
1521
|
+
tpb: int,
|
|
1522
|
+
) -> Optional[POWSolution]:
|
|
1523
|
+
"""
|
|
1524
|
+
Tries to solve the POW on a CUDA device for a block of nonces (nonce_start, nonce_start + update_interval * tpb
|
|
1525
|
+
"""
|
|
1526
|
+
solution, seal = solve_cuda(
|
|
1527
|
+
nonce_start,
|
|
1528
|
+
update_interval,
|
|
1529
|
+
tpb,
|
|
1530
|
+
block_and_hotkey_hash_bytes,
|
|
1531
|
+
difficulty,
|
|
1532
|
+
limit,
|
|
1533
|
+
dev_id,
|
|
1534
|
+
)
|
|
1535
|
+
|
|
1536
|
+
if solution != -1:
|
|
1537
|
+
# Check if solution is valid (i.e. not -1)
|
|
1538
|
+
return POWSolution(solution, block_number, difficulty, seal)
|
|
1539
|
+
|
|
1540
|
+
return None
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
def _solve_for_nonce_block(
|
|
1544
|
+
nonce_start: int,
|
|
1545
|
+
nonce_end: int,
|
|
1546
|
+
block_and_hotkey_hash_bytes: bytes,
|
|
1547
|
+
difficulty: int,
|
|
1548
|
+
limit: int,
|
|
1549
|
+
block_number: int,
|
|
1550
|
+
) -> Optional[POWSolution]:
|
|
1551
|
+
"""
|
|
1552
|
+
Tries to solve the POW for a block of nonces (nonce_start, nonce_end)
|
|
1553
|
+
"""
|
|
1554
|
+
for nonce in range(nonce_start, nonce_end):
|
|
1555
|
+
# Create seal.
|
|
1556
|
+
seal = _create_seal_hash(block_and_hotkey_hash_bytes, nonce)
|
|
1557
|
+
|
|
1558
|
+
# Check if seal meets difficulty
|
|
1559
|
+
if _seal_meets_difficulty(seal, difficulty, limit):
|
|
1560
|
+
# Found a solution, save it.
|
|
1561
|
+
return POWSolution(nonce, block_number, difficulty, seal)
|
|
1562
|
+
|
|
1563
|
+
return None
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
class CUDAException(Exception):
|
|
1567
|
+
"""An exception raised when an error occurs in the CUDA environment."""
|
|
1568
|
+
|
|
1569
|
+
|
|
1570
|
+
def _hex_bytes_to_u8_list(hex_bytes: bytes):
|
|
1571
|
+
hex_chunks = [int(hex_bytes[i : i + 2], 16) for i in range(0, len(hex_bytes), 2)]
|
|
1572
|
+
return hex_chunks
|
|
1573
|
+
|
|
1574
|
+
|
|
1575
|
+
def _create_seal_hash(block_and_hotkey_hash_bytes: bytes, nonce: int) -> bytes:
|
|
1576
|
+
"""
|
|
1577
|
+
Create a cryptographic seal hash from the given block and hotkey hash bytes and nonce.
|
|
1578
|
+
|
|
1579
|
+
This function generates a seal hash by combining the given block and hotkey hash bytes with a nonce.
|
|
1580
|
+
It first converts the nonce to a byte representation, then concatenates it with the first 64 hex
|
|
1581
|
+
characters of the block and hotkey hash bytes. The result is then hashed using SHA-256 followed by
|
|
1582
|
+
the Keccak-256 algorithm to produce the final seal hash.
|
|
1583
|
+
|
|
1584
|
+
:param block_and_hotkey_hash_bytes: The combined hash bytes of the block and hotkey.
|
|
1585
|
+
:param nonce: The nonce value used for hashing.
|
|
1586
|
+
|
|
1587
|
+
:return: The resulting seal hash.
|
|
1588
|
+
"""
|
|
1589
|
+
nonce_bytes = binascii.hexlify(nonce.to_bytes(8, "little"))
|
|
1590
|
+
pre_seal = nonce_bytes + binascii.hexlify(block_and_hotkey_hash_bytes)[:64]
|
|
1591
|
+
seal_sh256 = hashlib.sha256(bytearray(_hex_bytes_to_u8_list(pre_seal))).digest()
|
|
1592
|
+
kec = keccak.new(digest_bits=256)
|
|
1593
|
+
seal = kec.update(seal_sh256).digest()
|
|
1594
|
+
return seal
|
|
1595
|
+
|
|
1596
|
+
|
|
1597
|
+
def _seal_meets_difficulty(seal: bytes, difficulty: int, limit: int) -> bool:
|
|
1598
|
+
"""Determines if a seal meets the specified difficulty"""
|
|
1599
|
+
seal_number = int.from_bytes(seal, "big")
|
|
1600
|
+
product = seal_number * difficulty
|
|
1601
|
+
return product < limit
|
|
1602
|
+
|
|
1603
|
+
|
|
1604
|
+
def _hash_block_with_hotkey(block_bytes: bytes, hotkey_bytes: bytes) -> bytes:
|
|
1605
|
+
"""Hashes the block with the hotkey using Keccak-256 to get 32 bytes"""
|
|
1606
|
+
kec = keccak.new(digest_bits=256)
|
|
1607
|
+
kec = kec.update(bytearray(block_bytes + hotkey_bytes))
|
|
1608
|
+
block_and_hotkey_hash_bytes = kec.digest()
|
|
1609
|
+
return block_and_hotkey_hash_bytes
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
def _update_curr_block(
|
|
1613
|
+
curr_diff: Array,
|
|
1614
|
+
curr_block: Array,
|
|
1615
|
+
curr_block_num: Value,
|
|
1616
|
+
block_number: int,
|
|
1617
|
+
block_bytes: bytes,
|
|
1618
|
+
diff: int,
|
|
1619
|
+
hotkey_bytes: bytes,
|
|
1620
|
+
lock: Lock,
|
|
1621
|
+
):
|
|
1622
|
+
"""
|
|
1623
|
+
Update the current block data with the provided block information and difficulty.
|
|
1624
|
+
|
|
1625
|
+
This function updates the current block
|
|
1626
|
+
and its difficulty in a thread-safe manner. It sets the current block
|
|
1627
|
+
number, hashes the block with the hotkey, updates the current block bytes, and packs the difficulty.
|
|
1628
|
+
|
|
1629
|
+
:param curr_diff: Shared array to store the current difficulty.
|
|
1630
|
+
:param curr_block: Shared array to store the current block data.
|
|
1631
|
+
:param curr_block_num: Shared value to store the current block number.
|
|
1632
|
+
:param block_number: The block number to set as the current block number.
|
|
1633
|
+
:param block_bytes: The block data bytes to be hashed with the hotkey.
|
|
1634
|
+
:param diff: The difficulty value to be packed into the current difficulty array.
|
|
1635
|
+
:param hotkey_bytes: The hotkey bytes used for hashing the block.
|
|
1636
|
+
:param lock: A lock to ensure thread-safe updates.
|
|
1637
|
+
"""
|
|
1638
|
+
with lock:
|
|
1639
|
+
curr_block_num.value = block_number
|
|
1640
|
+
# Hash the block with the hotkey
|
|
1641
|
+
block_and_hotkey_hash_bytes = _hash_block_with_hotkey(block_bytes, hotkey_bytes)
|
|
1642
|
+
for i in range(32):
|
|
1643
|
+
curr_block[i] = block_and_hotkey_hash_bytes[i]
|
|
1644
|
+
_registration_diff_pack(diff, curr_diff)
|
|
1645
|
+
|
|
1646
|
+
|
|
1647
|
+
def get_cpu_count() -> int:
|
|
1648
|
+
try:
|
|
1649
|
+
return len(os.sched_getaffinity(0))
|
|
1650
|
+
except AttributeError:
|
|
1651
|
+
# macOS does not have sched_getaffinity
|
|
1652
|
+
return os.cpu_count()
|
|
1653
|
+
|
|
1654
|
+
|
|
1655
|
+
@dataclass
|
|
1656
|
+
class RegistrationStatistics:
|
|
1657
|
+
"""Statistics for a registration."""
|
|
1658
|
+
|
|
1659
|
+
time_spent_total: float
|
|
1660
|
+
rounds_total: int
|
|
1661
|
+
time_average: float
|
|
1662
|
+
time_spent: float
|
|
1663
|
+
hash_rate_perpetual: float
|
|
1664
|
+
hash_rate: float
|
|
1665
|
+
difficulty: int
|
|
1666
|
+
block_number: int
|
|
1667
|
+
block_hash: bytes
|
|
1668
|
+
|
|
1669
|
+
|
|
1670
|
+
def solve_cuda(
|
|
1671
|
+
nonce_start: np.int64,
|
|
1672
|
+
update_interval: np.int64,
|
|
1673
|
+
tpb: int,
|
|
1674
|
+
block_and_hotkey_hash_bytes: bytes,
|
|
1675
|
+
difficulty: int,
|
|
1676
|
+
limit: int,
|
|
1677
|
+
dev_id: int = 0,
|
|
1678
|
+
) -> tuple[np.int64, bytes]:
|
|
1679
|
+
"""
|
|
1680
|
+
Solves the PoW problem using CUDA.
|
|
1681
|
+
|
|
1682
|
+
:param nonce_start: Starting nonce.
|
|
1683
|
+
:param update_interval: Number of nonces to solve before updating block information.
|
|
1684
|
+
:param tpb: Threads per block.
|
|
1685
|
+
:param block_and_hotkey_hash_bytes: Keccak(Bytes of the block hash + bytes of the hotkey) 64 bytes.
|
|
1686
|
+
:param difficulty: Difficulty of the PoW problem.
|
|
1687
|
+
:param limit: Upper limit of the nonce.
|
|
1688
|
+
:param dev_id: The CUDA device ID
|
|
1689
|
+
|
|
1690
|
+
:return: (nonce, seal) corresponding to the solution. Returns -1 for nonce if no solution is found.
|
|
1691
|
+
"""
|
|
1692
|
+
|
|
1693
|
+
try:
|
|
1694
|
+
import cubit
|
|
1695
|
+
except ImportError:
|
|
1696
|
+
raise ImportError("Please install cubit")
|
|
1697
|
+
|
|
1698
|
+
upper = int(limit // difficulty)
|
|
1699
|
+
|
|
1700
|
+
upper_bytes = upper.to_bytes(32, byteorder="little", signed=False)
|
|
1701
|
+
|
|
1702
|
+
# Call cython function
|
|
1703
|
+
# int blockSize, uint64 nonce_start, uint64 update_interval, const unsigned char[:] limit,
|
|
1704
|
+
# const unsigned char[:] block_bytes, int dev_id
|
|
1705
|
+
block_and_hotkey_hash_hex = binascii.hexlify(block_and_hotkey_hash_bytes)[:64]
|
|
1706
|
+
|
|
1707
|
+
solution = cubit.solve_cuda(
|
|
1708
|
+
tpb,
|
|
1709
|
+
nonce_start,
|
|
1710
|
+
update_interval,
|
|
1711
|
+
upper_bytes,
|
|
1712
|
+
block_and_hotkey_hash_hex,
|
|
1713
|
+
dev_id,
|
|
1714
|
+
) # 0 is first GPU
|
|
1715
|
+
seal = None
|
|
1716
|
+
if solution != -1:
|
|
1717
|
+
seal = _create_seal_hash(block_and_hotkey_hash_hex, solution)
|
|
1718
|
+
if _seal_meets_difficulty(seal, difficulty, limit):
|
|
1719
|
+
return solution, seal
|
|
1720
|
+
else:
|
|
1721
|
+
return -1, b"\x00" * 32
|
|
1722
|
+
|
|
1723
|
+
return solution, seal
|
|
1724
|
+
|
|
1725
|
+
|
|
1726
|
+
def reset_cuda():
|
|
1727
|
+
"""
|
|
1728
|
+
Resets the CUDA environment.
|
|
1729
|
+
"""
|
|
1730
|
+
try:
|
|
1731
|
+
import cubit
|
|
1732
|
+
except ImportError:
|
|
1733
|
+
raise ImportError("Please install cubit")
|
|
1734
|
+
|
|
1735
|
+
cubit.reset_cuda()
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
def log_cuda_errors() -> str:
|
|
1739
|
+
"""
|
|
1740
|
+
Logs any CUDA errors.
|
|
1741
|
+
"""
|
|
1742
|
+
try:
|
|
1743
|
+
import cubit
|
|
1744
|
+
except ImportError:
|
|
1745
|
+
raise ImportError("Please install cubit")
|
|
1746
|
+
|
|
1747
|
+
f = io.StringIO()
|
|
1748
|
+
with redirect_stdout(f):
|
|
1749
|
+
cubit.log_cuda_errors()
|
|
1750
|
+
|
|
1751
|
+
s = f.getvalue()
|
|
1752
|
+
|
|
1753
|
+
return s
|
|
1754
|
+
|
|
1755
|
+
|
|
1756
|
+
async def swap_hotkey_extrinsic(
|
|
1757
|
+
meshtensor: "MeshtensorInterface",
|
|
1758
|
+
wallet: Wallet,
|
|
1759
|
+
new_wallet: Wallet,
|
|
1760
|
+
netuid: Optional[int] = None,
|
|
1761
|
+
proxy: Optional[str] = None,
|
|
1762
|
+
prompt: bool = False,
|
|
1763
|
+
decline: bool = False,
|
|
1764
|
+
quiet: bool = False,
|
|
1765
|
+
) -> tuple[bool, Optional[AsyncExtrinsicReceipt]]:
|
|
1766
|
+
"""
|
|
1767
|
+
Performs an extrinsic update for swapping two hotkeys on the chain
|
|
1768
|
+
|
|
1769
|
+
:return: Success
|
|
1770
|
+
"""
|
|
1771
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
1772
|
+
hk_ss58 = get_hotkey_pub_ss58(wallet)
|
|
1773
|
+
new_hk_ss58 = get_hotkey_pub_ss58(new_wallet)
|
|
1774
|
+
|
|
1775
|
+
netuids_registered = await meshtensor.get_netuids_for_hotkey(
|
|
1776
|
+
hk_ss58, block_hash=block_hash
|
|
1777
|
+
)
|
|
1778
|
+
netuids_registered_new_hotkey = await meshtensor.get_netuids_for_hotkey(
|
|
1779
|
+
new_hk_ss58, block_hash=block_hash
|
|
1780
|
+
)
|
|
1781
|
+
|
|
1782
|
+
if netuid is not None and netuid not in netuids_registered:
|
|
1783
|
+
print_error(
|
|
1784
|
+
f"Failed: Original hotkey {hk_ss58} is not registered on subnet {netuid}"
|
|
1785
|
+
)
|
|
1786
|
+
return False, None
|
|
1787
|
+
|
|
1788
|
+
elif not len(netuids_registered) > 0:
|
|
1789
|
+
print_error(
|
|
1790
|
+
f"Original hotkey [dark_orange]{hk_ss58}[/dark_orange] is not registered on any subnet. "
|
|
1791
|
+
f"Please register and try again"
|
|
1792
|
+
)
|
|
1793
|
+
return False, None
|
|
1794
|
+
|
|
1795
|
+
if netuid is not None:
|
|
1796
|
+
if netuid in netuids_registered_new_hotkey:
|
|
1797
|
+
print_error(
|
|
1798
|
+
f"Failed: New hotkey {new_hk_ss58} "
|
|
1799
|
+
f"is already registered on subnet {netuid}"
|
|
1800
|
+
)
|
|
1801
|
+
return False, None
|
|
1802
|
+
else:
|
|
1803
|
+
if len(netuids_registered_new_hotkey) > 0:
|
|
1804
|
+
print_error(
|
|
1805
|
+
f"Failed: New hotkey {new_hk_ss58} "
|
|
1806
|
+
f"is already registered on subnet(s) {netuids_registered_new_hotkey}"
|
|
1807
|
+
)
|
|
1808
|
+
return False, None
|
|
1809
|
+
|
|
1810
|
+
if not unlock_key(wallet).success:
|
|
1811
|
+
return False, None
|
|
1812
|
+
|
|
1813
|
+
if prompt:
|
|
1814
|
+
# Prompt user for confirmation.
|
|
1815
|
+
if netuid is not None:
|
|
1816
|
+
confirm_message = (
|
|
1817
|
+
f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
|
|
1818
|
+
f"[dark_orange]{hk_ss58} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
|
|
1819
|
+
f"[dark_orange]{new_hk_ss58} ({new_wallet.hotkey_str})[/dark_orange] on subnet {netuid}\n"
|
|
1820
|
+
"This operation will cost [bold cyan]1 MESH (recycled)[/bold cyan]"
|
|
1821
|
+
)
|
|
1822
|
+
else:
|
|
1823
|
+
confirm_message = (
|
|
1824
|
+
f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t"
|
|
1825
|
+
f"[dark_orange]{hk_ss58} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t"
|
|
1826
|
+
f"[dark_orange]{new_hk_ss58} ({new_wallet.hotkey_str})[/dark_orange] on all subnets\n"
|
|
1827
|
+
"This operation will cost [bold cyan]1 MESH (recycled)[/bold cyan]"
|
|
1828
|
+
)
|
|
1829
|
+
|
|
1830
|
+
if not confirm_action(confirm_message, decline=decline, quiet=quiet):
|
|
1831
|
+
return False, None
|
|
1832
|
+
print_verbose(
|
|
1833
|
+
f"Swapping {wallet.name}'s hotkey ({hk_ss58} - {wallet.hotkey_str}) with "
|
|
1834
|
+
f"{new_wallet.name}'s hotkey ({new_hk_ss58} - {new_wallet.hotkey_str})"
|
|
1835
|
+
)
|
|
1836
|
+
with console.status(":satellite: Swapping hotkeys...", spinner="aesthetic"):
|
|
1837
|
+
call_params = {
|
|
1838
|
+
"hotkey": hk_ss58,
|
|
1839
|
+
"new_hotkey": new_hk_ss58,
|
|
1840
|
+
"netuid": netuid,
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
call = await meshtensor.substrate.compose_call(
|
|
1844
|
+
call_module="MeshtensorModule",
|
|
1845
|
+
call_function="swap_hotkey",
|
|
1846
|
+
call_params=call_params,
|
|
1847
|
+
)
|
|
1848
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
1849
|
+
call=call, wallet=wallet, proxy=proxy
|
|
1850
|
+
)
|
|
1851
|
+
|
|
1852
|
+
if success:
|
|
1853
|
+
console.print(
|
|
1854
|
+
f"Hotkey {hk_ss58} ({wallet.hotkey_str}) swapped for new hotkey: "
|
|
1855
|
+
f"{new_hk_ss58} ({new_wallet.hotkey_str})"
|
|
1856
|
+
)
|
|
1857
|
+
return True, ext_receipt
|
|
1858
|
+
else:
|
|
1859
|
+
print_error(f"Failed: {err_msg}")
|
|
1860
|
+
time.sleep(0.5)
|
|
1861
|
+
return False, ext_receipt
|