-
Notifications
You must be signed in to change notification settings - Fork 74
Expand file tree
/
Copy pathregistration.py
More file actions
1846 lines (1595 loc) · 64.7 KB
/
registration.py
File metadata and controls
1846 lines (1595 loc) · 64.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import asyncio
import binascii
from contextlib import redirect_stdout
from dataclasses import dataclass
from datetime import timedelta
import functools
import hashlib
import io
import math
import multiprocessing as mp
from multiprocessing.queues import Queue as Queue_Type
from multiprocessing import Process, Event, Lock, Array, Value, Queue
import os
from queue import Empty, Full
import random
import time
import typing
from typing import Optional
import subprocess
from bittensor_wallet import Wallet
from Crypto.Hash import keccak
import numpy as np
from rich.prompt import Confirm
from rich.console import Console
from rich.status import Status
from async_substrate_interface.errors import SubstrateRequestException
from bittensor_cli.src import COLOR_PALETTE
from bittensor_cli.src.bittensor.chain_data import NeuronInfo
from bittensor_cli.src.bittensor.balances import Balance
from bittensor_cli.src.bittensor.utils import (
console,
err_console,
format_error_message,
millify,
get_human_readable,
print_verbose,
print_error,
unlock_key,
hex_to_bytes,
get_hotkey_pub_ss58,
)
if typing.TYPE_CHECKING:
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
def use_torch() -> bool:
"""Force the use of torch over numpy for certain operations."""
return True if os.getenv("USE_TORCH") == "1" else False
def legacy_torch_api_compat(func: typing.Callable):
"""
Convert function operating on numpy Input&Output to legacy torch Input&Output API if `use_torch()` is True.
:param func: Function with numpy Input/Output to be decorated.
:return: Decorated function
"""
@functools.wraps(func)
def decorated(*args, **kwargs):
if use_torch():
# if argument is a Torch tensor, convert it to numpy
args = [
arg.cpu().numpy() if isinstance(arg, torch.Tensor) else arg
for arg in args
]
kwargs = {
key: value.cpu().numpy() if isinstance(value, torch.Tensor) else value
for key, value in kwargs.items()
}
ret = func(*args, **kwargs)
if use_torch():
# if return value is a numpy array, convert it to Torch tensor
if isinstance(ret, np.ndarray):
ret = torch.from_numpy(ret)
return ret
return decorated
@functools.cache
def _get_real_torch():
try:
import torch as _real_torch
except ImportError:
_real_torch = None
return _real_torch
def log_no_torch_error():
err_console.print(
"This command requires torch. You can install torch"
" with `pip install torch` and run the command again."
)
@dataclass
class POWSolution:
"""A solution to the registration PoW problem."""
nonce: int
block_number: int
difficulty: int
seal: bytes
async def is_stale(self, subtensor: "SubtensorInterface") -> bool:
"""Returns True if the POW is stale.
This means the block the POW is solved for is within 3 blocks of the current block.
"""
current_block = await subtensor.substrate.get_block_number(None)
return self.block_number < current_block - 3
@dataclass
class RegistrationStatistics:
"""Statistics for a registration."""
time_spent_total: float
rounds_total: int
time_average: float
time_spent: float
hash_rate_perpetual: float
hash_rate: float
difficulty: int
block_number: int
block_hash: str
class RegistrationStatisticsLogger:
"""Logs statistics for a registration."""
console: Console
status: Optional[Status]
def __init__(self, console_: Console, output_in_place: bool = True) -> None:
self.console = console_
if output_in_place:
self.status = self.console.status("Solving")
else:
self.status = None
def start(self) -> None:
if self.status is not None:
self.status.start()
def stop(self) -> None:
if self.status is not None:
self.status.stop()
@classmethod
def get_status_message(
cls, stats: RegistrationStatistics, verbose: bool = False
) -> str:
"""
Provides a message of the current status of the block solving as a str for a logger or stdout
"""
message = (
"Solving\n"
+ f"Time Spent (total): [bold white]{timedelta(seconds=stats.time_spent_total)}[/bold white]\n"
+ (
f"Time Spent This Round: {timedelta(seconds=stats.time_spent)}\n"
+ f"Time Spent Average: {timedelta(seconds=stats.time_average)}\n"
if verbose
else ""
)
+ f"Registration Difficulty: [bold white]{millify(stats.difficulty)}[/bold white]\n"
+ f"Iters (Inst/Perp): [bold white]{get_human_readable(stats.hash_rate, 'H')}/s / "
+ f"{get_human_readable(stats.hash_rate_perpetual, 'H')}/s[/bold white]\n"
+ f"Block Number: [bold white]{stats.block_number}[/bold white]\n"
+ f"Block Hash: [bold white]{stats.block_hash.encode('utf-8')}[/bold white]\n"
)
return message
def update(self, stats: RegistrationStatistics, verbose: bool = False) -> None:
"""
Passes the current status to the logger
"""
if self.status is not None:
self.status.update(self.get_status_message(stats, verbose=verbose))
else:
self.console.log(self.get_status_message(stats, verbose=verbose))
class _SolverBase(Process):
"""
A process that solves the registration PoW problem.
:param proc_num: The number of the process being created.
:param num_proc: The total number of processes running.
:param update_interval: The number of nonces to try to solve before checking for a new block.
:param finished_queue: The queue to put the process number when a process finishes each update_interval.
Used for calculating the average time per update_interval across all processes.
:param solution_queue: The queue to put the solution the process has found during the pow solve.
:param stop_event: The event to set by the main process when all the solver processes should stop.
The solver process will check for the event after each update_interval.
The solver process will stop when the event is set.
Used to stop the solver processes when a solution is found.
:param curr_block: The array containing this process's current block hash.
The main process will set the array to the new block hash when a new block is finalized in the
network. The solver process will get the new block hash from this array when newBlockEvent is set
:param curr_block_num: The value containing this process's current block number.
The main process will set the value to the new block number when a new block is finalized in
the network. The solver process will get the new block number from this value when
new_block_event is set.
:param curr_diff: The array containing this process's current difficulty. The main process will set the array to
the new difficulty when a new block is finalized in the network. The solver process will get the
new difficulty from this array when newBlockEvent is set.
:param check_block: The lock to prevent this process from getting the new block data while the main process is
updating the data.
:param limit: The limit of the pow solve for a valid solution.
:var new_block_event: The event to set by the main process when a new block is finalized in the network.
The solver process will check for the event after each update_interval.
The solver process will get the new block hash and difficulty and start solving for a new
nonce.
"""
proc_num: int
num_proc: int
update_interval: int
finished_queue: Queue_Type
solution_queue: Queue_Type
new_block_event: Event
stop_event: Event
hotkey_bytes: bytes
curr_block: Array
curr_block_num: Value
curr_diff: Array
check_block: Lock
limit: int
def __init__(
self,
proc_num,
num_proc,
update_interval,
finished_queue,
solution_queue,
stop_event,
curr_block,
curr_block_num,
curr_diff,
check_block,
limit,
):
Process.__init__(self, daemon=True)
self.proc_num = proc_num
self.num_proc = num_proc
self.update_interval = update_interval
self.finished_queue = finished_queue
self.solution_queue = solution_queue
self.new_block_event = Event()
self.new_block_event.clear()
self.curr_block = curr_block
self.curr_block_num = curr_block_num
self.curr_diff = curr_diff
self.check_block = check_block
self.stop_event = stop_event
self.limit = limit
def run(self):
raise NotImplementedError("_SolverBase is an abstract class")
@staticmethod
def create_shared_memory() -> tuple[Array, Value, Array]:
"""Creates shared memory for the solver processes to use."""
curr_block = Array("h", 32, lock=True) # byte array
curr_block_num = Value("i", 0, lock=True) # int
curr_diff = Array("Q", [0, 0], lock=True) # [high, low]
return curr_block, curr_block_num, curr_diff
class _Solver(_SolverBase):
"""
Performs POW Solution
"""
def run(self):
block_number: int
block_and_hotkey_hash_bytes: bytes
block_difficulty: int
nonce_limit = int(math.pow(2, 64)) - 1
# Start at random nonce
nonce_start = random.randint(0, nonce_limit)
nonce_end = nonce_start + self.update_interval
while not self.stop_event.is_set():
if self.new_block_event.is_set():
with self.check_block:
block_number = self.curr_block_num.value
block_and_hotkey_hash_bytes = bytes(self.curr_block)
block_difficulty = _registration_diff_unpack(self.curr_diff)
self.new_block_event.clear()
# Do a block of nonces
solution = _solve_for_nonce_block(
nonce_start,
nonce_end,
block_and_hotkey_hash_bytes,
block_difficulty,
self.limit,
block_number,
)
if solution is not None:
self.solution_queue.put(solution)
try:
# Send time
self.finished_queue.put_nowait(self.proc_num)
except Full:
pass
nonce_start = random.randint(0, nonce_limit)
nonce_start = nonce_start % nonce_limit
nonce_end = nonce_start + self.update_interval
class _CUDASolver(_SolverBase):
"""
Performs POW Solution using CUDA
"""
dev_id: int
tpb: int
def __init__(
self,
proc_num,
num_proc,
update_interval,
finished_queue,
solution_queue,
stop_event,
curr_block,
curr_block_num,
curr_diff,
check_block,
limit,
dev_id: int,
tpb: int,
):
super().__init__(
proc_num,
num_proc,
update_interval,
finished_queue,
solution_queue,
stop_event,
curr_block,
curr_block_num,
curr_diff,
check_block,
limit,
)
self.dev_id = dev_id
self.tpb = tpb
def run(self):
block_number: int = 0 # dummy value
block_and_hotkey_hash_bytes: bytes = b"0" * 32 # dummy value
block_difficulty: int = int(math.pow(2, 64)) - 1 # dummy value
nonce_limit = int(math.pow(2, 64)) - 1 # U64MAX
# Start at random nonce
nonce_start = random.randint(0, nonce_limit)
while not self.stop_event.is_set():
if self.new_block_event.is_set():
with self.check_block:
block_number = self.curr_block_num.value
block_and_hotkey_hash_bytes = bytes(self.curr_block)
block_difficulty = _registration_diff_unpack(self.curr_diff)
self.new_block_event.clear()
# Do a block of nonces
solution = _solve_for_nonce_block_cuda(
nonce_start,
self.update_interval,
block_and_hotkey_hash_bytes,
block_difficulty,
self.limit,
block_number,
self.dev_id,
self.tpb,
)
if solution is not None:
self.solution_queue.put(solution)
try:
# Signal that a nonce_block was finished using queue
# send our proc_num
self.finished_queue.put(self.proc_num)
except Full:
pass
# increase nonce by number of nonces processed
nonce_start += self.update_interval * self.tpb
nonce_start = nonce_start % nonce_limit
class LazyLoadedTorch:
def __bool__(self):
return bool(_get_real_torch())
def __getattr__(self, name):
if real_torch := _get_real_torch():
return getattr(real_torch, name)
else:
log_no_torch_error()
raise ImportError("torch not installed")
if typing.TYPE_CHECKING:
import torch
else:
torch = LazyLoadedTorch()
class MaxSuccessException(Exception):
"""
Raised when the POW Solver has reached the max number of successful solutions
"""
class MaxAttemptsException(Exception):
"""
Raised when the POW Solver has reached the max number of attempts
"""
async def is_hotkey_registered(
subtensor: "SubtensorInterface", netuid: int, hotkey_ss58: str
) -> bool:
"""Checks to see if the hotkey is registered on a given netuid"""
_result = await subtensor.query(
module="SubtensorModule",
storage_function="Uids",
params=[netuid, hotkey_ss58],
)
if _result is not None:
return True
else:
return False
async def register_extrinsic(
subtensor: "SubtensorInterface",
wallet: Wallet,
netuid: int,
wait_for_inclusion: bool = False,
wait_for_finalization: bool = True,
prompt: bool = False,
max_allowed_attempts: int = 3,
output_in_place: bool = True,
cuda: bool = False,
dev_id: typing.Union[list[int], int] = 0,
tpb: int = 256,
num_processes: Optional[int] = None,
update_interval: Optional[int] = None,
log_verbose: bool = False,
) -> bool:
"""Registers the wallet to the chain.
:param subtensor: initialized SubtensorInterface object to use for chain interactions
:param wallet: Bittensor wallet object.
:param netuid: The ``netuid`` of the subnet to register on.
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
`False` if the extrinsic fails to enter the block within the timeout.
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
or returns `False` if the extrinsic fails to be finalized within the timeout.
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
:param max_allowed_attempts: Maximum number of attempts to register the wallet.
:param output_in_place: Whether the POW solving should be outputted to the console as it goes along.
:param cuda: If `True`, the wallet should be registered using CUDA device(s).
:param dev_id: The CUDA device id to use, or a list of device ids.
:param tpb: The number of threads per block (CUDA).
:param num_processes: The number of processes to use to register.
:param update_interval: The number of nonces to solve between updates.
:param log_verbose: If `True`, the registration process will log more information.
:return: `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion,
the response is `True`.
"""
async def get_neuron_for_pubkey_and_subnet():
uid = await subtensor.query(
"SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
)
if uid is None:
return NeuronInfo.get_null_neuron()
result = await subtensor.neuron_for_uid(
uid=uid,
netuid=netuid,
block_hash=subtensor.substrate.last_block_hash,
)
return result
print_verbose("Checking subnet status")
if not await subtensor.subnet_exists(netuid):
err_console.print(
f":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{netuid}[/bold white] does not exist."
)
return False
with console.status(
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...",
spinner="aesthetic",
) as status:
neuron = await get_neuron_for_pubkey_and_subnet()
if not neuron.is_null:
print_error(
f"Wallet {wallet} is already registered on subnet {neuron.netuid} with uid {neuron.uid}",
status,
)
return True
if prompt:
if not Confirm.ask(
f"Continue Registration?\n"
f" hotkey [{COLOR_PALETTE.G.HK}]({wallet.hotkey_str})[/{COLOR_PALETTE.G.HK}]:"
f"\t[{COLOR_PALETTE.G.HK}]{get_hotkey_pub_ss58(wallet)}[/{COLOR_PALETTE.G.HK}]\n"
f" coldkey [{COLOR_PALETTE.G.CK}]({wallet.name})[/{COLOR_PALETTE.G.CK}]:"
f"\t[{COLOR_PALETTE.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE.G.CK}]\n"
f" network:\t\t[{COLOR_PALETTE.G.LINKS}]{subtensor.network}[/{COLOR_PALETTE.G.LINKS}]\n"
):
return False
if not torch:
log_no_torch_error()
return False
# Attempt rolling registration.
attempts = 1
pow_result: Optional[POWSolution]
while True:
console.print(
":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts)
)
# Solve latest POW.
if cuda:
if not torch.cuda.is_available():
if prompt:
console.print("CUDA is not available.")
return False
pow_result = await create_pow(
subtensor,
wallet,
netuid,
output_in_place,
cuda=cuda,
dev_id=dev_id,
tpb=tpb,
num_processes=num_processes,
update_interval=update_interval,
log_verbose=log_verbose,
)
else:
pow_result = await create_pow(
subtensor,
wallet,
netuid,
output_in_place,
cuda=cuda,
num_processes=num_processes,
update_interval=update_interval,
log_verbose=log_verbose,
)
# pow failed
if not pow_result:
# might be registered already on this subnet
is_registered = await is_hotkey_registered(
subtensor, netuid=netuid, hotkey_ss58=get_hotkey_pub_ss58(wallet)
)
if is_registered:
err_console.print(
f":white_heavy_check_mark: [dark_sea_green3]Already registered on netuid:{netuid}[/dark_sea_green3]"
)
return True
# pow successful, proceed to submit pow to chain for registration
else:
with console.status(":satellite: Submitting POW..."):
# check if pow result is still valid
while not await pow_result.is_stale(subtensor=subtensor):
call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="register",
call_params={
"netuid": netuid,
"block_number": pow_result.block_number,
"nonce": pow_result.nonce,
"work": [int(byte_) for byte_ in pow_result.seal],
"hotkey": get_hotkey_pub_ss58(wallet),
"coldkey": wallet.coldkeypub.ss58_address,
},
)
extrinsic = await subtensor.substrate.create_signed_extrinsic(
call=call, keypair=wallet.hotkey
)
response = await subtensor.substrate.submit_extrinsic(
extrinsic,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
)
if not wait_for_finalization and not wait_for_inclusion:
success, err_msg = True, ""
else:
success = await response.is_success
if not success:
success, err_msg = (
False,
format_error_message(await response.error_message),
)
# Look error here
# https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs
if "HotKeyAlreadyRegisteredInSubNet" in err_msg:
console.print(
f":white_heavy_check_mark: [dark_sea_green3]Already Registered on "
f"[bold]subnet:{netuid}[/bold][/dark_sea_green3]"
)
return True
err_console.print(
f":cross_mark: [red]Failed[/red]: {err_msg}"
)
await asyncio.sleep(0.5)
# Successful registration, final check for neuron and pubkey
if success:
console.print(":satellite: Checking Registration status...")
is_registered = await is_hotkey_registered(
subtensor,
netuid=netuid,
hotkey_ss58=get_hotkey_pub_ss58(wallet),
)
if is_registered:
console.print(
":white_heavy_check_mark: [dark_sea_green3]Registered[/dark_sea_green3]"
)
return True
else:
# neuron not found, try again
err_console.print(
":cross_mark: [red]Unknown error. Neuron not found.[/red]"
)
continue
else:
# Exited loop because pow is no longer valid.
err_console.print("[red]POW is stale.[/red]")
# Try again.
continue
if attempts < max_allowed_attempts:
# Failed registration, retry pow
attempts += 1
err_console.print(
":satellite: Failed registration, retrying pow ...({attempts}/{max_allowed_attempts})"
)
else:
# Failed to register after max attempts.
err_console.print("[red]No more attempts.[/red]")
return False
async def burned_register_extrinsic(
subtensor: "SubtensorInterface",
wallet: Wallet,
netuid: int,
old_balance: Balance,
wait_for_inclusion: bool = True,
wait_for_finalization: bool = True,
era: Optional[int] = None,
) -> tuple[bool, str]:
"""Registers the wallet to chain by recycling TAO.
:param subtensor: The SubtensorInterface object to use for the call, initialized
:param wallet: Bittensor wallet object.
:param netuid: The `netuid` of the subnet to register on.
:param old_balance: The wallet balance prior to the registration burn.
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
`False` if the extrinsic fails to enter the block within the timeout.
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
or returns `False` if the extrinsic fails to be finalized within the timeout.
:param era: the period (in blocks) for which the transaction should remain valid.
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
:return: (success, msg), where success is `True` if extrinsic was finalized or included in the block. If we did not
wait for finalization/inclusion, the response is `True`.
"""
if not (unlock_status := unlock_key(wallet, print_out=False)).success:
return False, unlock_status.message
with console.status(
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]...",
spinner="aesthetic",
) as status:
my_uid = await subtensor.query(
"SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
)
block_hash = await subtensor.substrate.get_chain_head()
print_verbose("Checking if already registered", status)
neuron = await subtensor.neuron_for_uid(
uid=my_uid, netuid=netuid, block_hash=block_hash
)
if not era:
current_block, tempo, blocks_since_last_step = await asyncio.gather(
subtensor.substrate.get_block_number(block_hash=block_hash),
subtensor.get_hyperparameter(
"Tempo", netuid=netuid, block_hash=block_hash
),
subtensor.query(
"SubtensorModule",
"BlocksSinceLastStep",
[netuid],
block_hash=block_hash,
),
)
validity_period = tempo - blocks_since_last_step
era_ = {
"period": validity_period,
"current": current_block,
}
else:
era_ = {"period": era}
if not neuron.is_null:
console.print(
":white_heavy_check_mark: [dark_sea_green3]Already Registered[/dark_sea_green3]:\n"
f"uid: [{COLOR_PALETTE.G.NETUID_EXTRA}]{neuron.uid}[/{COLOR_PALETTE.G.NETUID_EXTRA}]\n"
f"netuid: [{COLOR_PALETTE.G.NETUID}]{neuron.netuid}[/{COLOR_PALETTE.G.NETUID}]\n"
f"hotkey: [{COLOR_PALETTE.G.HK}]{neuron.hotkey}[/{COLOR_PALETTE.G.HK}]\n"
f"coldkey: [{COLOR_PALETTE.G.CK}]{neuron.coldkey}[/{COLOR_PALETTE.G.CK}]"
)
return True, "Already registered"
with console.status(
":satellite: Recycling TAO for Registration...", spinner="aesthetic"
):
call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="burned_register",
call_params={
"netuid": netuid,
"hotkey": get_hotkey_pub_ss58(wallet),
},
)
success, err_msg = await subtensor.sign_and_send_extrinsic(
call, wallet, wait_for_inclusion, wait_for_finalization, era=era_
)
if not success:
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
await asyncio.sleep(0.5)
return False, err_msg
# Successful registration, final check for neuron and pubkey
else:
with console.status(":satellite: Checking Balance...", spinner="aesthetic"):
block_hash = await subtensor.substrate.get_chain_head()
new_balance, netuids_for_hotkey, my_uid = await asyncio.gather(
subtensor.get_balance(
wallet.coldkeypub.ss58_address,
block_hash=block_hash,
reuse_block=False,
),
subtensor.get_netuids_for_hotkey(
get_hotkey_pub_ss58(wallet), block_hash=block_hash
),
subtensor.query(
"SubtensorModule", "Uids", [netuid, get_hotkey_pub_ss58(wallet)]
),
)
console.print(
"Balance:\n"
f" [blue]{old_balance}[/blue] :arrow_right: "
f"[{COLOR_PALETTE.S.STAKE_AMOUNT}]{new_balance}[/{COLOR_PALETTE.S.STAKE_AMOUNT}]"
)
if len(netuids_for_hotkey) > 0:
console.print(
f":white_heavy_check_mark: [green]Registered on netuid {netuid} with UID {my_uid}[/green]"
)
return True, f"Registered on {netuid} with UID {my_uid}"
else:
# neuron not found, try again
err_console.print(
":cross_mark: [red]Unknown error. Neuron not found.[/red]"
)
return False, "Unknown error. Neuron not found."
async def run_faucet_extrinsic(
subtensor: "SubtensorInterface",
wallet: Wallet,
wait_for_inclusion: bool = False,
wait_for_finalization: bool = True,
prompt: bool = False,
max_allowed_attempts: int = 3,
output_in_place: bool = True,
cuda: bool = False,
dev_id: int = 0,
tpb: int = 256,
num_processes: Optional[int] = None,
update_interval: Optional[int] = None,
log_verbose: bool = True,
max_successes: int = 3,
) -> tuple[bool, str]:
r"""Runs a continual POW to get a faucet of TAO on the test net.
:param subtensor: The subtensor interface object used to run the extrinsic
:param wallet: Bittensor wallet object.
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`,
or returns `False` if the extrinsic fails to enter the block within the timeout.
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
or returns `False` if the extrinsic fails to be finalized within the timeout.
:param max_allowed_attempts: Maximum number of attempts to register the wallet.
:param output_in_place: Whether to output logging data as the process runs.
:param cuda: If `True`, the wallet should be registered using CUDA device(s).
:param dev_id: The CUDA device id to use
:param tpb: The number of threads per block (CUDA).
:param num_processes: The number of processes to use to register.
:param update_interval: The number of nonces to solve between updates.
:param log_verbose: If `True`, the registration process will log more information.
:param max_successes: The maximum number of successful faucet runs for the wallet.
:return: `True` if extrinsic was finalized or included in the block. If we did not wait for
finalization/inclusion, the response is also `True`
"""
if prompt:
if not Confirm.ask(
"Run Faucet?\n"
f" wallet name: [bold white]{wallet.name}[/bold white]\n"
f" coldkey: [bold white]{wallet.coldkeypub.ss58_address}[/bold white]\n"
f" network: [bold white]{subtensor}[/bold white]"
):
return False, ""
if not torch:
log_no_torch_error()
return False, "Requires torch"
# Unlock coldkey
if not (unlock_status := unlock_key(wallet, print_out=False)).success:
return False, unlock_status.message
# Get previous balance.
old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
# Attempt rolling registration.
attempts = 1
successes = 1
pow_result: Optional[POWSolution]
while True:
try:
account_nonce = await subtensor.substrate.get_account_nonce(
wallet.coldkey.ss58_address
)
pow_result = None
while pow_result is None or await pow_result.is_stale(subtensor=subtensor):
# Solve latest POW.
if cuda:
if not torch.cuda.is_available():
if prompt:
err_console.print("CUDA is not available.")
return False, "CUDA is not available."
pow_result = await create_pow(
subtensor,
wallet,
-1,
output_in_place,
cuda=cuda,
dev_id=dev_id,
tpb=tpb,
num_processes=num_processes,
update_interval=update_interval,
log_verbose=log_verbose,
)
else:
pow_result = await create_pow(
subtensor,
wallet,
-1,
output_in_place,
cuda=cuda,
num_processes=num_processes,
update_interval=update_interval,
log_verbose=log_verbose,
)
call = await subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="faucet",
call_params={
"block_number": pow_result.block_number,
"nonce": pow_result.nonce,
"work": [int(byte_) for byte_ in pow_result.seal],
},
)
extrinsic = await subtensor.substrate.create_signed_extrinsic(
call=call, keypair=wallet.coldkey, nonce=account_nonce
)
response = await subtensor.substrate.submit_extrinsic(
extrinsic,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
)
# process if registration successful, try again if pow is still valid
if not await response.is_success:
err_console.print(
f":cross_mark: [red]Failed[/red]: "
f"{format_error_message(await response.error_message)}"
)
if attempts == max_allowed_attempts:
raise MaxAttemptsException
attempts += 1
# Wait a bit before trying again
time.sleep(1)
# Successful registration
else:
new_balance = await subtensor.get_balance(
wallet.coldkeypub.ss58_address
)
console.print(
f"Balance: [blue]{old_balance}[/blue] :arrow_right:"
f" [green]{new_balance}[/green]"
)
old_balance = new_balance
if successes == max_successes:
raise MaxSuccessException
attempts = 1 # Reset attempts on success
successes += 1
except KeyboardInterrupt:
return True, "Done"
except MaxSuccessException:
return True, f"Max successes reached: {3}"
except MaxAttemptsException:
return False, f"Max attempts reached: {max_allowed_attempts}"
async def _check_for_newest_block_and_update(
subtensor: "SubtensorInterface",
netuid: int,
old_block_number: int,
hotkey_bytes: bytes,
curr_diff: Array,
curr_block: Array,
curr_block_num: Value,
update_curr_block: typing.Callable,
check_block: Lock,
solvers: list[_Solver],
curr_stats: RegistrationStatistics,
) -> int:
"""
Checks for a new block and updates the current block information if a new block is found.
:param subtensor: The subtensor object to use for getting the current block.
:param netuid: The netuid to use for retrieving the difficulty.
:param old_block_number: The old block number to check against.
:param hotkey_bytes: The bytes of the hotkey's pubkey.
:param curr_diff: The current difficulty as a multiprocessing array.
:param curr_block: Where the current block is stored as a multiprocessing array.
:param curr_block_num: Where the current block number is stored as a multiprocessing value.
:param update_curr_block: A function that updates the current block.
:param check_block: A mp lock that is used to check for a new block.
:param solvers: A list of solvers to update the current block for.
:param curr_stats: The current registration statistics to update.
:return: The current block number.
"""
block_number = await subtensor.substrate.get_block_number(None)
if block_number != old_block_number:
old_block_number = block_number
# update block information
block_number, difficulty, block_hash = await _get_block_with_retry(
subtensor=subtensor, netuid=netuid
)
block_bytes = hex_to_bytes(block_hash)
update_curr_block(
curr_diff,
curr_block,
curr_block_num,