From 92fcfd79ec1ac37a55893ee7ed63bd9946f7feae Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 5 Apr 2022 22:26:22 -0400 Subject: [PATCH 01/86] added cuda solver --- bittensor/_subtensor/subtensor_impl.py | 6 +- bittensor/utils/__init__.py | 139 ++++++++++++++++++++----- bittensor/utils/register_cuda.py | 72 +++++++++++++ 3 files changed, 190 insertions(+), 27 deletions(-) create mode 100644 bittensor/utils/register_cuda.py diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 75f8aa5d23..37723367f4 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -472,9 +472,12 @@ def register ( # Attempt rolling registration. attempts = 1 + cuda = False + if torch.cuda.is_available(): + cuda = Confirm.ask("Would you like to try CUDA registration?\n") while True: # Solve latest POW. - pow_result = bittensor.utils.create_pow( self, wallet ) + pow_result = bittensor.utils.create_pow( self, wallet, cuda ) with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: # pow failed @@ -532,7 +535,6 @@ def register ( status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) continue - def serve ( self, wallet: 'bittensor.wallet', diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 0372080c2d..4893ad08cf 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,21 +1,25 @@ import binascii -import multiprocessing import ctypes -import struct +import datetime import hashlib -from Crypto.Hash import keccak import math -import bittensor +import multiprocessing +import numbers import random -import rich +import struct import time -import torch -import numbers +from typing import Any, List, Optional, Tuple, Union + +import bittensor import pandas import requests -from substrateinterface.utils import ss58 +import rich +import torch +from Crypto.Hash import keccak from substrateinterface import Keypair, KeypairType -from typing import Any, Tuple, List, Union, Optional +from substrateinterface.utils import ss58 + +from .register_cuda import reset_cuda, solve_cuda def indexed_values_to_dataframe ( @@ -130,23 +134,21 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, upd block_hash = subtensor.substrate.get_block_hash( block_number ) block_bytes = block_hash.encode('utf-8')[2:] + nonce = 0 limit = int(math.pow(2,256)) - 1 - nonce_limit = int(math.pow(2,64)) - 1 - nonce = random.randint( 0, nonce_limit ) start_time = time.time() console = bittensor.__console__ status = console.status("Solving") - #found_solution = multiprocessing.Value('q', -1, lock=False) # int - found_solution = multiprocessing.Array('Q', [0, 0, 0], lock=True) # [valid, nonce_high, nonce_low] + found_solution = multiprocessing.Value('q', -1, lock=False) # int best_raw = struct.pack("d", float('inf')) best = multiprocessing.Array(ctypes.c_char, best_raw, lock=True) # byte array to get around int size of ctypes best_seal = multiprocessing.Array('h', 32, lock=True) # short array should hold bytes (0, 256) with multiprocessing.Pool(processes=num_processes, initializer=initProcess_, initargs=(solve_, found_solution, best, best_seal)) as pool: status.start() - while found_solution[0] == 0 and not wallet.is_registered(subtensor): + while found_solution.value == -1 and not wallet.is_registered(subtensor): iterable = [( nonce_start, nonce_start + update_interval , block_bytes, @@ -157,7 +159,6 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, upd result = pool.starmap(solve_, iterable=iterable) old_nonce = nonce nonce += update_interval*num_processes - nonce = nonce % nonce_limit itrs_per_sec = update_interval*num_processes / (time.time() - start_time) start_time = time.time() difficulty = subtensor.difficulty @@ -178,18 +179,17 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, upd status.update(message.replace(" ", "")) # exited while, found_solution contains the nonce or wallet is registered - if found_solution[0] == 0: # didn't find solution + if found_solution.value == -1: # didn't find solution status.stop() return None, None, None, None, None - found_unpacked: int = found_solution[1] << 32 | found_solution[2] - nonce, block_number, block_hash, difficulty, seal = result[ math.floor( (found_unpacked-old_nonce) / update_interval) ] + nonce, block_number, block_hash, difficulty, seal = result[ math.floor( (found_solution.value-old_nonce) / update_interval) ] status.stop() return nonce, block_number, block_hash, difficulty, seal def initProcess_(f, found_solution, best, best_seal): f.found = found_solution - f.best = best + f.best = best f.best_seal = best_seal def solve_(nonce_start, nonce_end, block_bytes, difficulty, block_hash, block_number, limit): @@ -207,10 +207,7 @@ def solve_(nonce_start, nonce_end, block_bytes, difficulty, block_hash, block_nu product = seal_number * difficulty if product < limit: - with solve_.found.get_lock(): - solve_.found[0] = 1; - solve_.found[1] = nonce >> 32 - solve_.found[2] = nonce & 0xFFFFFFFF # low 32 bits + solve_.found.value = nonce return (nonce, block_number, block_hash, difficulty, seal) if (product - limit) < best_local: @@ -227,9 +224,101 @@ def solve_(nonce_start, nonce_end, block_bytes, difficulty, block_hash, block_nu solve_.best_seal[i] = best_seal_local[i] return None + +def get_human_readable(num, suffix="H"): + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: + if abs(num) < 1000.0: + return f"{num:3.1f}{unit}{suffix}" + num /= 1000.0 + return f"{num:.1f}Y{suffix}" + +def millify(n: int): + millnames = ['',' K',' M',' B',' T'] + n = float(n) + millidx = max(0,min(len(millnames)-1, + int(math.floor(0 if n == 0 else math.log10(abs(n))/3)))) + + return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) -def create_pow( subtensor, wallet ): - nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet ) + +def solve_for_difficulty_fast_cuda( subtensor, wallet, update_interval: int = 100000, TPB: int = 1024 ) -> Tuple[int, int, Any, int, Any]: + if not torch.cuda.is_available(): + raise Exception("CUDA not available") + + block_number = subtensor.get_current_block() + difficulty = subtensor.difficulty + block_hash = subtensor.substrate.get_block_hash( block_number ) + while block_hash == None: + block_hash = subtensor.substrate.get_block_hash( block_number ) + block_bytes = block_hash.encode('utf-8')[2:] + + nonce = 0 + limit = int(math.pow(2,256)) - 1 + start_time = time.time() + + console = bittensor.__console__ + status = console.status("Solving") + + + solution = -1 + start_time = time.time() + interval_time = start_time + + status.start() + while solution == -1 and not wallet.is_registered(subtensor): + solution, seal = solve_cuda(nonce, + update_interval, + TPB, + block_bytes, + difficulty, + limit) + + if (solution != -1): + # Attempt to reset CUDA device + reset_cuda() + status.stop() + new_bn = subtensor.get_current_block() + print(f"Found solution for bn: {block_number}; Newest: {new_bn}") + return solution, block_number, block_hash, difficulty, seal + + nonce += update_interval*TPB + if (nonce >= int(math.pow(2,63))): + nonce = 0 + itrs_per_sec = update_interval*TPB / (time.time() - interval_time) + interval_time = time.time() + difficulty = subtensor.difficulty + block_number = subtensor.get_current_block() + block_hash = subtensor.substrate.get_block_hash( block_number) + while block_hash == None: + block_hash = subtensor.substrate.get_block_hash( block_number) + block_bytes = block_hash.encode('utf-8')[2:] + + message = f"""Solving + time spent: {datetime.timedelta(seconds=time.time() - start_time)} + Nonce: [bold white]{nonce}[/bold white] + Difficulty: [bold white]{millify(difficulty)}[/bold white] + Iters: [bold white]{get_human_readable(int(itrs_per_sec), "H")}/s[/bold white] + Block: [bold white]{block_number}[/bold white] + Block_hash: [bold white]{block_hash.encode('utf-8')}[/bold white]""" + status.update(message.replace(" ", "")) + + # exited while, found_solution contains the nonce or wallet is registered + if solution == -1: # didn't find solution + reset_cuda() + status.stop() + return None, None, None, None, None + + else: + reset_cuda() + # Shouldn't get here + status.stop() + return None, None, None, None, None + +def create_pow( subtensor, wallet, cuda: bool = False ): + if cuda: + nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast_cuda( subtensor, wallet ) + else: + nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet ) return None if nonce is None else { 'nonce': nonce, 'difficulty': difficulty, diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py new file mode 100644 index 0000000000..7dc6fd746d --- /dev/null +++ b/bittensor/utils/register_cuda.py @@ -0,0 +1,72 @@ +from typing import Tuple +import math + +import hashlib +import binascii +from bittensor_register_cuda import solve_cuda as solve_cuda_c, reset_cuda as reset_cuda_c + +import numpy as np + +def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, difficulty: np.int64, limit: np.int64) -> Tuple[np.int64, bytes]: + """ + Solves the PoW problem using CUDA. + Args: + nonce_start: int32 + Starting nonce. + update_interval: int32 + Number of nonces to solve before updating block information. + TPB: int + Threads per block. + block_bytes: bytes + Bytes of the block hash. 64 bytes. + difficulty: int32 + Difficulty of the PoW problem. + limit: int32 + Upper limit of the nonce. + Returns: + Tuple[int32, bytes] + Tuple of the nonce and the seal corresponding to the solution. + Returns -1 for nonce if no solution is found. + """ + upper = int(limit // difficulty) + + upper_bytes = upper.to_bytes(32, byteorder='little', signed=False) + + def seal_meets_difficulty( seal:bytes, difficulty:int ): + seal_number = int.from_bytes(seal, "big") + product = seal_number * difficulty + limit = int(math.pow(2,256))- 1 + upper = int(limit // difficulty) + if product > limit: + return False + else: + return True + def hex_bytes_to_u8_list( hex_bytes: bytes ): + hex_chunks = [int(hex_bytes[i:i+2], 16) for i in range(0, len(hex_bytes), 2)] + return hex_chunks + + def create_seal_hash( block_bytes:bytes, nonce:int ) -> bytes: + nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) + pre_seal = nonce_bytes + block_bytes + seal = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + return seal + + # Call cython function + # int blockSize, int64 nonce_start, uint64 update_interval, + # uint64 difficulty, const unsigned char[:] limit, const unsigned char[:] block_bytes + solution = solve_cuda_c(TPB, nonce_start, update_interval, upper_bytes, block_bytes) + seal = None + if solution != -1: + seal = create_seal_hash(block_bytes, solution) + if seal_meets_difficulty(seal, difficulty): + return solution, seal + else: + return -1, b'\x00' * 32 + + return solution, seal + +def reset_cuda(): + """ + Resets the CUDA environment. + """ + reset_cuda_c() \ No newline at end of file From a97fbf357828dd0e5da51d362f1dfe629142b654 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 17 May 2022 10:49:16 -0400 Subject: [PATCH 02/86] boost versions to fix pip error --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 27f5b5372d..1fb380368b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ cryptography>=3.1.1 idna>=2.10 fuzzywuzzy==0.18.0 python-levenshtein==0.12 -google-api-python-client +google-api-python-client>=2.6.0 grpcio==1.42.0 grpcio-tools==1.42.0 loguru @@ -17,7 +17,7 @@ netaddr==0.8.0 pandas psutil password_strength==0.0.3.post2 -protobuf==3.13.0 +protobuf>=3.13.0 pycryptodome==3.11.0 py-sr25519-bindings>=0.1.4,<1 py-ed25519-bindings>=1.0,<2 From 61b21fcb67c7e0e819081c2be520f6072622fc44 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 17 May 2022 10:50:20 -0400 Subject: [PATCH 03/86] allow choosing device id --- bittensor/_subtensor/subtensor_impl.py | 17 ++++++++++++---- bittensor/utils/__init__.py | 28 ++++++++++++++++++-------- bittensor/utils/register_cuda.py | 8 +++++--- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 37723367f4..5b0f3b929c 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -15,7 +15,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import torch -from rich.prompt import Confirm +from rich.prompt import Confirm, Prompt from typing import List, Dict, Union from multiprocessing import Process @@ -473,11 +473,20 @@ def register ( # Attempt rolling registration. attempts = 1 cuda = False - if torch.cuda.is_available(): - cuda = Confirm.ask("Would you like to try CUDA registration?\n") + dev_id: int = 0 + if prompt: + if torch.cuda.is_available(): + cuda = Confirm.ask("Would you like to try CUDA registration?\n") + if cuda: + devices: List[int] = [str(x) for x in range(torch.cuda.device_count())] + dev_id = Prompt.ask("Which GPU would you like to use?", choices=devices, default="0") + dev_id = int(dev_id) while True: # Solve latest POW. - pow_result = bittensor.utils.create_pow( self, wallet, cuda ) + if cuda: + pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id ) + else: + pow_result = bittensor.utils.create_pow( self, wallet, cuda ) with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: # pow failed diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 4893ad08cf..d91d366da2 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -240,8 +240,21 @@ def millify(n: int): return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) - -def solve_for_difficulty_fast_cuda( subtensor, wallet, update_interval: int = 100000, TPB: int = 1024 ) -> Tuple[int, int, Any, int, Any]: +def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'bittensor.Wallet', update_interval: int = 100000, TPB: int = 512, dev_id: int = 0 ) -> Tuple[int, int, Any, int, Any]: + """ + Solves the registration fast using CUDA + Args: + subtensor: bittensor.Subtensor + The subtensor node to grab blocks + wallet: bittensor.Wallet + The wallet to register + update_interval: int + The number of nonces to try before checking for more blocks + TPB: int + The number of threads per block. CUDA param that should match the GPU capability + dev_id: int + The CUDA device ID to execute the registration on + """ if not torch.cuda.is_available(): raise Exception("CUDA not available") @@ -265,13 +278,14 @@ def solve_for_difficulty_fast_cuda( subtensor, wallet, update_interval: int = 10 interval_time = start_time status.start() - while solution == -1 and not wallet.is_registered(subtensor): + while solution == -1: #and not wallet.is_registered(subtensor): solution, seal = solve_cuda(nonce, update_interval, TPB, block_bytes, difficulty, - limit) + limit, + dev_id) if (solution != -1): # Attempt to reset CUDA device @@ -314,9 +328,9 @@ def solve_for_difficulty_fast_cuda( subtensor, wallet, update_interval: int = 10 status.stop() return None, None, None, None, None -def create_pow( subtensor, wallet, cuda: bool = False ): +def create_pow( subtensor, wallet, cuda: bool = False, dev_id: int = 0 ): if cuda: - nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast_cuda( subtensor, wallet ) + nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast_cuda( subtensor, wallet, dev_id=dev_id ) else: nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet ) return None if nonce is None else { @@ -414,5 +428,3 @@ def is_valid_destination_address( address: Union[str, bytes] ) -> bool: return False return True - - diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py index 7dc6fd746d..4325afa09c 100644 --- a/bittensor/utils/register_cuda.py +++ b/bittensor/utils/register_cuda.py @@ -23,6 +23,8 @@ def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block Difficulty of the PoW problem. limit: int32 Upper limit of the nonce. + dev_id: int (default=0) + The CUDA device ID Returns: Tuple[int32, bytes] Tuple of the nonce and the seal corresponding to the solution. @@ -52,9 +54,9 @@ def create_seal_hash( block_bytes:bytes, nonce:int ) -> bytes: return seal # Call cython function - # int blockSize, int64 nonce_start, uint64 update_interval, - # uint64 difficulty, const unsigned char[:] limit, const unsigned char[:] block_bytes - solution = solve_cuda_c(TPB, nonce_start, update_interval, upper_bytes, block_bytes) + # int blockSize, uint64 nonce_start, uint64 update_interval, const unsigned char[:] limit, + # const unsigned char[:] block_bytes, int dev_id + solution = solve_cuda_c(TPB, nonce_start, update_interval, upper_bytes, block_bytes, dev_id) # 0 is first GPU seal = None if solution != -1: seal = create_seal_hash(block_bytes, solution) From a55d554dba06516af31ae72b6d5b2b47bc54fcfc Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 17 May 2022 10:50:34 -0400 Subject: [PATCH 04/86] fix solution check to use keccak --- bittensor/utils/register_cuda.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py index 4325afa09c..dd2a4a53f9 100644 --- a/bittensor/utils/register_cuda.py +++ b/bittensor/utils/register_cuda.py @@ -1,13 +1,15 @@ -from typing import Tuple -import math - -import hashlib import binascii -from bittensor_register_cuda import solve_cuda as solve_cuda_c, reset_cuda as reset_cuda_c +import hashlib +import math +from typing import Tuple import numpy as np +from bittensor_register_cuda import reset_cuda as reset_cuda_c +from bittensor_register_cuda import solve_cuda as solve_cuda_c +from Crypto.Hash import keccak + -def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, difficulty: np.int64, limit: np.int64) -> Tuple[np.int64, bytes]: +def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, difficulty: np.int64, limit: np.int64, dev_id: int = 0) -> Tuple[np.int64, bytes]: """ Solves the PoW problem using CUDA. Args: @@ -37,12 +39,10 @@ def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block def seal_meets_difficulty( seal:bytes, difficulty:int ): seal_number = int.from_bytes(seal, "big") product = seal_number * difficulty - limit = int(math.pow(2,256))- 1 - upper = int(limit // difficulty) - if product > limit: - return False - else: - return True + limit = int(math.pow(2,256))- 1 + + return product < limit + def hex_bytes_to_u8_list( hex_bytes: bytes ): hex_chunks = [int(hex_bytes[i:i+2], 16) for i in range(0, len(hex_bytes), 2)] return hex_chunks @@ -50,7 +50,9 @@ def hex_bytes_to_u8_list( hex_bytes: bytes ): def create_seal_hash( block_bytes:bytes, nonce:int ) -> bytes: nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) pre_seal = nonce_bytes + block_bytes - seal = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + kec = keccak.new(digest_bits=256) + seal = kec.update( seal_sh256 ).digest() return seal # Call cython function @@ -59,6 +61,7 @@ def create_seal_hash( block_bytes:bytes, nonce:int ) -> bytes: solution = solve_cuda_c(TPB, nonce_start, update_interval, upper_bytes, block_bytes, dev_id) # 0 is first GPU seal = None if solution != -1: + print(f"Checking solution: {solution}") seal = create_seal_hash(block_bytes, solution) if seal_meets_difficulty(seal, difficulty): return solution, seal @@ -71,4 +74,4 @@ def reset_cuda(): """ Resets the CUDA environment. """ - reset_cuda_c() \ No newline at end of file + reset_cuda_c() From 752913dbe889ec274d76f4efd0292c847b6c8026 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Wed, 18 May 2022 16:22:51 -0400 Subject: [PATCH 05/86] adds params for cuda and dev_id to register --- bittensor/_subtensor/subtensor_impl.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 5b0f3b929c..14b3113293 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -440,7 +440,9 @@ def register ( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, - max_allowed_attempts: int = 3 + max_allowed_attempts: int = 3, + cuda: bool = False, + dev_id: int = 0 ) -> bool: r""" Registers the wallet to chain. Args: @@ -472,8 +474,6 @@ def register ( # Attempt rolling registration. attempts = 1 - cuda = False - dev_id: int = 0 if prompt: if torch.cuda.is_available(): cuda = Confirm.ask("Would you like to try CUDA registration?\n") From 99a9f45b8671374aed41df9211bcb436657a0d9a Mon Sep 17 00:00:00 2001 From: camfairchild Date: Wed, 18 May 2022 16:23:07 -0400 Subject: [PATCH 06/86] list devices by name during selection --- bittensor/_subtensor/subtensor_impl.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 14b3113293..2a8335a69e 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -478,7 +478,11 @@ def register ( if torch.cuda.is_available(): cuda = Confirm.ask("Would you like to try CUDA registration?\n") if cuda: - devices: List[int] = [str(x) for x in range(torch.cuda.device_count())] + devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] + device_names: List[str] = [torch.cuda.get_device_name(x) for x in range(torch.cuda.device_count())] + print("Available CUDA devices:") + for i, device in enumerate(devices): + print(" {}: {}".format(device, device_names[i])) dev_id = Prompt.ask("Which GPU would you like to use?", choices=devices, default="0") dev_id = int(dev_id) while True: From e6adbba57413f7b532b1d45ebd068ecf0916a177 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Wed, 18 May 2022 16:23:23 -0400 Subject: [PATCH 07/86] add block number logging --- bittensor/utils/__init__.py | 1 + bittensor/utils/register_cuda.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index d91d366da2..a448885a48 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -283,6 +283,7 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b update_interval, TPB, block_bytes, + block_number, difficulty, limit, dev_id) diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py index dd2a4a53f9..29fb29a390 100644 --- a/bittensor/utils/register_cuda.py +++ b/bittensor/utils/register_cuda.py @@ -9,7 +9,7 @@ from Crypto.Hash import keccak -def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, difficulty: np.int64, limit: np.int64, dev_id: int = 0) -> Tuple[np.int64, bytes]: +def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, bn: int, difficulty: np.int64, limit: np.int64, dev_id: int = 0) -> Tuple[np.int64, bytes]: """ Solves the PoW problem using CUDA. Args: @@ -61,7 +61,7 @@ def create_seal_hash( block_bytes:bytes, nonce:int ) -> bytes: solution = solve_cuda_c(TPB, nonce_start, update_interval, upper_bytes, block_bytes, dev_id) # 0 is first GPU seal = None if solution != -1: - print(f"Checking solution: {solution}") + print(f"Checking solution: {solution} for bn: {bn}") seal = create_seal_hash(block_bytes, solution) if seal_meets_difficulty(seal, difficulty): return solution, seal From b788fb40de25509efc38138576eef74fe65cdb54 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 2 Jun 2022 13:57:08 -0400 Subject: [PATCH 08/86] fix calculation of hashrate --- bittensor/utils/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index a448885a48..954218b738 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -296,10 +296,10 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b print(f"Found solution for bn: {block_number}; Newest: {new_bn}") return solution, block_number, block_hash, difficulty, seal - nonce += update_interval*TPB + nonce += update_interval if (nonce >= int(math.pow(2,63))): nonce = 0 - itrs_per_sec = update_interval*TPB / (time.time() - interval_time) + itrs_per_sec = update_interval / (time.time() - interval_time) interval_time = time.time() difficulty = subtensor.difficulty block_number = subtensor.get_current_block() From 14e1293b9eb31ae40351fb85bb2e446528c14f55 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Thu, 2 Jun 2022 14:04:40 -0400 Subject: [PATCH 09/86] fix update interval default --- bittensor/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 954218b738..a05c2858f5 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -240,7 +240,7 @@ def millify(n: int): return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) -def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'bittensor.Wallet', update_interval: int = 100000, TPB: int = 512, dev_id: int = 0 ) -> Tuple[int, int, Any, int, Any]: +def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'bittensor.Wallet', update_interval: int = 5_000_000, TPB: int = 512, dev_id: int = 0 ) -> Tuple[int, int, Any, int, Any]: """ Solves the registration fast using CUDA Args: From 17d945f0f956424d092dea2847b18fc340e00076 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Fri, 3 Jun 2022 18:12:16 -0400 Subject: [PATCH 10/86] add --TPB arg to register --- bittensor/_cli/__init__.py | 7 +++++++ bittensor/_cli/cli_impl.py | 2 +- bittensor/_subtensor/subtensor_impl.py | 5 +++-- bittensor/utils/__init__.py | 4 ++-- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 1a8886f8e2..9fc6bc1d0f 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -234,6 +234,13 @@ def config() -> 'bittensor.config': 'register', help='''Register a wallet to a network.''' ) + register_parser.add_argument( + '--TPB', + type=int, + default=256, + help='''Set the number of Threads per block for CUDA.''' + ) + unstake_parser = cmd_parsers.add_parser( 'unstake', help='''Unstake from hotkey accounts.''' diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 9176fc4494..54918b2c11 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -226,7 +226,7 @@ def register( self ): """ wallet = bittensor.wallet( config = self.config ) subtensor = bittensor.subtensor( config = self.config ) - subtensor.register( wallet = wallet, prompt = not self.config.no_prompt) + subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, TPB = self.config.get('TPB', None)) def transfer( self ): r""" Transfer token of amount to destination. diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 2a8335a69e..1b9d830104 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -442,7 +442,8 @@ def register ( prompt: bool = False, max_allowed_attempts: int = 3, cuda: bool = False, - dev_id: int = 0 + dev_id: int = 0, + TPB: int = 256, ) -> bool: r""" Registers the wallet to chain. Args: @@ -488,7 +489,7 @@ def register ( while True: # Solve latest POW. if cuda: - pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id ) + pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB ) else: pow_result = bittensor.utils.create_pow( self, wallet, cuda ) with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index a05c2858f5..aa97586ad4 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -329,9 +329,9 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b status.stop() return None, None, None, None, None -def create_pow( subtensor, wallet, cuda: bool = False, dev_id: int = 0 ): +def create_pow( subtensor, wallet, cuda: bool = False, dev_id: int = 0, tpb: int = 256 ): if cuda: - nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast_cuda( subtensor, wallet, dev_id=dev_id ) + nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast_cuda( subtensor, wallet, dev_id=dev_id, TPB=tpb ) else: nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet ) return None if nonce is None else { From 32ffc0ccc8e43931c21d97b79dff50450836779a Mon Sep 17 00:00:00 2001 From: camfairchild Date: Fri, 3 Jun 2022 18:28:32 -0400 Subject: [PATCH 11/86] add update_interval flag --- bittensor/_cli/__init__.py | 6 ++++++ bittensor/_cli/cli_impl.py | 2 +- bittensor/_subtensor/subtensor_impl.py | 3 ++- bittensor/utils/__init__.py | 4 ++-- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 9fc6bc1d0f..1c8b8ca485 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -240,6 +240,12 @@ def config() -> 'bittensor.config': default=256, help='''Set the number of Threads per block for CUDA.''' ) + register_parser.add_argument( + '--update_interval', + type=int, + default=1_000_000, + help='''Set the number of nonces per network update.''' + ) unstake_parser = cmd_parsers.add_parser( 'unstake', diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 54918b2c11..350c5ecb52 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -226,7 +226,7 @@ def register( self ): """ wallet = bittensor.wallet( config = self.config ) subtensor = bittensor.subtensor( config = self.config ) - subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, TPB = self.config.get('TPB', None)) + subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, TPB = self.config.get('TPB', None), update_interval = self.config.get('update_interval', None) ) def transfer( self ): r""" Transfer token of amount to destination. diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 1b9d830104..2c4d97aecb 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -444,6 +444,7 @@ def register ( cuda: bool = False, dev_id: int = 0, TPB: int = 256, + update_interval: int = 1_000_000, ) -> bool: r""" Registers the wallet to chain. Args: @@ -489,7 +490,7 @@ def register ( while True: # Solve latest POW. if cuda: - pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB ) + pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, update_interval ) else: pow_result = bittensor.utils.create_pow( self, wallet, cuda ) with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index aa97586ad4..497dbbddb4 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -329,9 +329,9 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b status.stop() return None, None, None, None, None -def create_pow( subtensor, wallet, cuda: bool = False, dev_id: int = 0, tpb: int = 256 ): +def create_pow( subtensor, wallet, cuda: bool = False, dev_id: int = 0, tpb: int = 256, update_interval: int = 1_000_000 ): if cuda: - nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast_cuda( subtensor, wallet, dev_id=dev_id, TPB=tpb ) + nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast_cuda( subtensor, wallet, dev_id=dev_id, TPB=tpb, update_interval=update_interval ) else: nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet ) return None if nonce is None else { From 772397205a7e15fce8ecd145ea9d7dd08360c339 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Mon, 20 Jun 2022 11:23:02 -0400 Subject: [PATCH 12/86] switch back to old looping/work structure --- bittensor/_cli/__init__.py | 69 ++++++++++++++++++++++---- bittensor/_cli/cli_impl.py | 9 +++- bittensor/_subtensor/__init__.py | 6 +++ bittensor/_subtensor/subtensor_impl.py | 15 ++---- bittensor/utils/__init__.py | 4 +- 5 files changed, 79 insertions(+), 24 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 1c8b8ca485..db2e969693 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -18,15 +18,18 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +import argparse import os import sys -import argparse +from typing import List import bittensor -from rich.prompt import Prompt -from rich.prompt import Confirm +import torch +from rich.prompt import Confirm, Prompt from substrateinterface.utils.ss58 import ss58_decode, ss58_encode + from . import cli_impl + console = bittensor.__console__ class cli: @@ -233,18 +236,42 @@ def config() -> 'bittensor.config': register_parser = cmd_parsers.add_parser( 'register', help='''Register a wallet to a network.''' + ) + register_parser.add_argument( + '--cuda.use_cuda', + '--subtensor.cuda.use_cuda', + dest='subtensor.cuda.use_cuda', + default=bittensor.defaults.subtensor.cuda.use_cuda, + help='''Set true to use CUDA.''', + action='store_true', + required=False ) register_parser.add_argument( - '--TPB', + '--cuda.dev_id', + '--subtensor.cuda.dev_id', + dest='subtensor.cuda.dev_id', type=int, - default=256, - help='''Set the number of Threads per block for CUDA.''' + default=bittensor.defaults.subtensor.cuda.dev_id, + help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', + required=False ) register_parser.add_argument( - '--update_interval', + '--cuda.TPB', + '--subtensor.cuda.TPB', + dest='subtensor.cuda.TPB', type=int, - default=1_000_000, - help='''Set the number of nonces per network update.''' + default=bittensor.defaults.subtensor.cuda.TPB, + help='''Set the number of Threads Per Block for CUDA.''', + required=False + ) + register_parser.add_argument( + '--cuda.update_interval', + '--subtensor.cuda.update_interval', + dest='subtensor.cuda.update_interval', + type=int, + default=bittensor.defaults.subtensor.cuda.update_interval, + help='''Set the number of nonces per network update.''', + required=False ) unstake_parser = cmd_parsers.add_parser( @@ -732,6 +759,28 @@ def check_register_config( config: 'bittensor.Config' ): hotkey = Prompt.ask("Enter hotkey name", default = bittensor.defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) + if not config.no_prompt and config.subtensor.cuda.use_cuda == bittensor.defaults.subtensor.cuda.use_cuda: + # Ask about cuda registration only if a CUDA device is available. + if torch.cuda.is_available(): + cuda = Confirm.ask("Would you like to try CUDA registration?\n") + config.subtensor.cuda.use_cuda = cuda + # Only ask about which CUDA device if the user has more than one CUDA device. + if cuda and config.subtensor.cuda.dev_id == bittensor.defaults.subtensor.cuda.dev_id and torch.cuda.device_count > 0: + devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] + device_names: List[str] = [torch.cuda.get_device_name(x) for x in range(torch.cuda.device_count())] + console.print("Available CUDA devices:") + choices_str: str = "" + for i, device in enumerate(devices): + choices_str += (" {}: {}\n".format(device, device_names[i])) + console.print(choices_str) + dev_id = Prompt.ask("Which GPU would you like to use?", choices=devices, default=str(bittensor.defaults.subtensor.cuda.dev_id)) + try: + dev_id = int(dev_id) + except ValueError: + console.error(":cross_mark:[red]Invalid GPU device[/red] [bold white]{}[/bold white]\nAvailable CUDA devices:{}".format(dev_id, choices_str)) + sys.exit(1) + config.subtensor.cuda.dev_id = dev_id + def check_new_coldkey_config( config: 'bittensor.Config' ): if config.wallet.name == bittensor.defaults.wallet.name and not config.no_prompt: wallet_name = Prompt.ask("Enter wallet name", default = bittensor.defaults.wallet.name) @@ -798,4 +847,4 @@ def check_help_config( config: 'bittensor.Config'): def check_update_config( config: 'bittensor.Config'): if not config.no_prompt: answer = Prompt.ask('This will update the local bittensor package', choices = ['Y','N'], default = 'Y') - config.answer = answer \ No newline at end of file + config.answer = answer diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 350c5ecb52..55c00f5ff0 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -226,7 +226,14 @@ def register( self ): """ wallet = bittensor.wallet( config = self.config ) subtensor = bittensor.subtensor( config = self.config ) - subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, TPB = self.config.get('TPB', None), update_interval = self.config.get('update_interval', None) ) + subtensor.register( + wallet = wallet, + prompt = not self.config.no_prompt, + TPB = self.config.subtensor.cuda.get('TPB', None), + update_interval = self.config.subtensor.cuda.get('update_interval', None), + cuda = self.config.subtensor.cuda.get('use_cuda', None), + dev_id = self.config.subtensor.cuda.get('dev_id', None) + ) def transfer( self ): r""" Transfer token of amount to destination. diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 5817db48d8..f916caf2c4 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -195,6 +195,12 @@ def add_defaults(cls, defaults ): defaults.subtensor.network = os.getenv('BT_SUBTENSOR_NETWORK') if os.getenv('BT_SUBTENSOR_NETWORK') != None else 'nakamoto' defaults.subtensor.chain_endpoint = os.getenv('BT_SUBTENSOR_CHAIN_ENDPOINT') if os.getenv('BT_SUBTENSOR_CHAIN_ENDPOINT') != None else None defaults.subtensor._mock = os.getenv('BT_SUBTENSOR_MOCK') if os.getenv('BT_SUBTENSOR_MOCK') != None else False + defaults.subtensor.cuda = bittensor.Config() + + defaults.subtensor.cuda.dev_id = 0 + defaults.subtensor.cuda.use_cuda = False + defaults.subtensor.cuda.update_interval = 10_000 + defaults.subtensor.cuda.TPB = 256 @staticmethod def check_config( config: 'bittensor.Config' ): diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 2c4d97aecb..5a723108e5 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -476,20 +476,13 @@ def register ( # Attempt rolling registration. attempts = 1 - if prompt: - if torch.cuda.is_available(): - cuda = Confirm.ask("Would you like to try CUDA registration?\n") - if cuda: - devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] - device_names: List[str] = [torch.cuda.get_device_name(x) for x in range(torch.cuda.device_count())] - print("Available CUDA devices:") - for i, device in enumerate(devices): - print(" {}: {}".format(device, device_names[i])) - dev_id = Prompt.ask("Which GPU would you like to use?", choices=devices, default="0") - dev_id = int(dev_id) while True: # Solve latest POW. if cuda: + if not torch.cuda.is_available(): + if prompt: + bittensor.__console__.error('CUDA is not available.') + return False pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, update_interval ) else: pow_result = bittensor.utils.create_pow( self, wallet, cuda ) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 497dbbddb4..646cf6d14b 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -296,10 +296,10 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b print(f"Found solution for bn: {block_number}; Newest: {new_bn}") return solution, block_number, block_hash, difficulty, seal - nonce += update_interval + nonce += (TPB * update_interval) if (nonce >= int(math.pow(2,63))): nonce = 0 - itrs_per_sec = update_interval / (time.time() - interval_time) + itrs_per_sec = (TPB * update_interval) / (time.time() - interval_time) interval_time = time.time() difficulty = subtensor.difficulty block_number = subtensor.get_current_block() From 6166829487e5e7838d72f7bfe46526e130a0ccba Mon Sep 17 00:00:00 2001 From: camfairchild Date: Tue, 21 Jun 2022 15:06:33 -0400 Subject: [PATCH 13/86] change typing --- bittensor/utils/register_cuda.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py index 29fb29a390..baf1fc7f04 100644 --- a/bittensor/utils/register_cuda.py +++ b/bittensor/utils/register_cuda.py @@ -9,26 +9,26 @@ from Crypto.Hash import keccak -def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, bn: int, difficulty: np.int64, limit: np.int64, dev_id: int = 0) -> Tuple[np.int64, bytes]: +def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, bn: int, difficulty: int, limit: int, dev_id: int = 0) -> Tuple[np.int64, bytes]: """ Solves the PoW problem using CUDA. Args: - nonce_start: int32 + nonce_start: int64 Starting nonce. - update_interval: int32 + update_interval: int64 Number of nonces to solve before updating block information. TPB: int Threads per block. block_bytes: bytes Bytes of the block hash. 64 bytes. - difficulty: int32 + difficulty: int256 Difficulty of the PoW problem. - limit: int32 + limit: int256 Upper limit of the nonce. dev_id: int (default=0) The CUDA device ID Returns: - Tuple[int32, bytes] + Tuple[int64, bytes] Tuple of the nonce and the seal corresponding to the solution. Returns -1 for nonce if no solution is found. """ From 66c23652c32dacbc982eceb3935dc59aad3be4b2 Mon Sep 17 00:00:00 2001 From: camfairchild Date: Fri, 24 Jun 2022 10:39:03 -0400 Subject: [PATCH 14/86] device count is a function --- bittensor/_cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index db2e969693..9e1b0b5c07 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -765,7 +765,7 @@ def check_register_config( config: 'bittensor.Config' ): cuda = Confirm.ask("Would you like to try CUDA registration?\n") config.subtensor.cuda.use_cuda = cuda # Only ask about which CUDA device if the user has more than one CUDA device. - if cuda and config.subtensor.cuda.dev_id == bittensor.defaults.subtensor.cuda.dev_id and torch.cuda.device_count > 0: + if cuda and config.subtensor.cuda.dev_id == bittensor.defaults.subtensor.cuda.dev_id and torch.cuda.device_count() > 0: devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] device_names: List[str] = [torch.cuda.get_device_name(x) for x in range(torch.cuda.device_count())] console.print("Available CUDA devices:") From 816bcd757290b87e8e4543af99c346a04a5e057c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 11 Jul 2022 15:34:16 -0400 Subject: [PATCH 15/86] stop early if wallet registered --- bittensor/utils/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 646cf6d14b..acdd2d398b 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -278,7 +278,7 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b interval_time = start_time status.start() - while solution == -1: #and not wallet.is_registered(subtensor): + while solution == -1 and not wallet.is_registered(subtensor): solution, seal = solve_cuda(nonce, update_interval, TPB, From e55fc612521a6dcffba665ea1a8ebc2f8c24ed10 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 25 Jul 2022 11:30:32 -0400 Subject: [PATCH 16/86] add update interval and num proc flag --- bittensor/_cli/__init__.py | 18 ++++++++++++++++++ bittensor/_cli/cli_impl.py | 2 +- bittensor/_subtensor/subtensor_impl.py | 6 ++++-- bittensor/utils/__init__.py | 7 +++++-- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 9bafaede00..30f2e9cca4 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -516,6 +516,24 @@ def config() -> 'bittensor.config': help='''Set true to protect the generated bittensor key with a password.''', default=False, ) + register_parser.add_argument( + '--num_processes', + '--num', + '-n', + dest='num_processes', + help="Number of processors to use for registration", + type=int, + default=None, + ) + register_parser.add_argument( + '--update_interval', + '-u', + dest='update_interval', + help="The number of nonces to process before checking for next block during registration", + type=int, + default=None, + ) + bittensor.wallet.add_args( register_parser ) bittensor.subtensor.add_args( register_parser ) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 5f9991f337..794e6a2cdd 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -227,7 +227,7 @@ def register( self ): """ wallet = bittensor.wallet( config = self.config ) subtensor = bittensor.subtensor( config = self.config ) - subtensor.register( wallet = wallet, prompt = not self.config.no_prompt) + subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, num_processes = self.config.num_processes, update_interval = self.config.update_interval ) def transfer( self ): r""" Transfer token of amount to destination. diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 75f8aa5d23..90f1f87c10 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -440,7 +440,9 @@ def register ( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, - max_allowed_attempts: int = 3 + max_allowed_attempts: int = 3, + num_processes: int = None, + update_interval: int = None, ) -> bool: r""" Registers the wallet to chain. Args: @@ -474,7 +476,7 @@ def register ( attempts = 1 while True: # Solve latest POW. - pow_result = bittensor.utils.create_pow( self, wallet ) + pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval ) with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: # pow failed diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 0372080c2d..dc6705b813 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -122,6 +122,9 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, upd """ if num_processes == None: num_processes = multiprocessing.cpu_count() + + if update_interval is None: + update_interval = 500_000 block_number = subtensor.get_current_block() difficulty = subtensor.difficulty @@ -228,8 +231,8 @@ def solve_(nonce_start, nonce_end, block_bytes, difficulty, block_hash, block_nu return None -def create_pow( subtensor, wallet ): - nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet ) +def create_pow( subtensor, wallet, num_processes: int = None, update_interval: int = None ) -> Optional[Dict[str, Any]]: + nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet, num_processes=num_processes, update_interval=update_interval ) return None if nonce is None else { 'nonce': nonce, 'difficulty': difficulty, From 9b4637dbdf813991c9d1c9b060a47ed65f5db7b0 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 25 Jul 2022 11:41:37 -0400 Subject: [PATCH 17/86] add better number output --- bittensor/utils/__init__.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index dc6705b813..aef2528740 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -102,7 +102,23 @@ def solve_for_difficulty( block_hash, difficulty ): break return nonce, seal -def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, update_interval: int = 500000 ) -> Tuple[int, int, Any, int, Any]: + +def get_human_readable(num, suffix="H"): + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: + if abs(num) < 1000.0: + return f"{num:3.1f}{unit}{suffix}" + num /= 1000.0 + return f"{num:.1f}Y{suffix}" + + +def millify(n: int): + millnames = ['',' K',' M',' B',' T'] + n = float(n) + millidx = max(0,min(len(millnames)-1, + int(math.floor(0 if n == 0 else math.log10(abs(n))/3)))) + + return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) + """ Solves the POW for registration using multiprocessing. Args: @@ -172,11 +188,8 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, upd with best_seal.get_lock(): message = f"""Solving time spent: {time.time() - start_time} - Nonce: [bold white]{nonce}[/bold white] - Difficulty: [bold white]{difficulty}[/bold white] - Iters: [bold white]{int(itrs_per_sec)}/s[/bold white] - Block: [bold white]{block_number}[/bold white] - Block_hash: [bold white]{block_hash.encode('utf-8')}[/bold white] + Difficulty: [bold white]{millify(difficulty)}[/bold white] + Iters: [bold white]{get_human_readable(int(itrs_per_sec), 'H')}/s[/bold white] Best: [bold white]{binascii.hexlify(bytes(best_seal) or bytes(0))}[/bold white]""" status.update(message.replace(" ", "")) From 4befcb587288627c5c95042d57032446c71dea03 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 25 Jul 2022 11:43:04 -0400 Subject: [PATCH 18/86] optimize multiproc cpu reg keeping proc until solution --- bittensor/utils/__init__.py | 324 ++++++++++++++++++++++++++---------- 1 file changed, 232 insertions(+), 92 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index aef2528740..899ce04694 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,13 +1,15 @@ import binascii +from dataclasses import dataclass import multiprocessing -import ctypes +from queue import Empty import struct import hashlib from Crypto.Hash import keccak import math -import bittensor + import random -import rich +import bittensor +import ctypes import time import torch import numbers @@ -15,7 +17,7 @@ import requests from substrateinterface.utils import ss58 from substrateinterface import Keypair, KeypairType -from typing import Any, Tuple, List, Union, Optional +from typing import Any, Tuple, List, Union, Optional, Dict def indexed_values_to_dataframe ( @@ -76,10 +78,12 @@ def u8_list_to_hex( values: list ): return total def create_seal_hash( block_hash:bytes, nonce:int ) -> bytes: - nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) block_bytes = block_hash.encode('utf-8')[2:] + nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) pre_seal = nonce_bytes + block_bytes - seal = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + kec = keccak.new(digest_bits=256) + seal = kec.update( seal_sh256 ).digest() return seal def seal_meets_difficulty( seal:bytes, difficulty:int ): @@ -119,6 +123,109 @@ def millify(n: int): return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) +@dataclass +class POWSolution: + """A solution to the registration PoW problem.""" + nonce: int + block_number: int + difficulty: int + seal: bytes + +class Solver(multiprocessing.Process): + proc_num: int + num_proc: int + update_interval: int + best_queue: multiprocessing.Queue + time_queue: multiprocessing.Queue + solution_queue: multiprocessing.Queue + newBlockEvent: multiprocessing.Event + stopEvent: multiprocessing.Event + curr_block: multiprocessing.Array + curr_block_num: multiprocessing.Value + curr_diff: multiprocessing.Array + check_block: multiprocessing.Lock + limit: int + + def __init__(self, proc_num, num_proc, update_interval, best_queue, time_queue, solution_queue, stopEvent, curr_block, curr_block_num, curr_diff, check_block, limit): + multiprocessing.Process.__init__(self) + self.proc_num = proc_num + self.num_proc = num_proc + self.update_interval = update_interval + self.best_queue = best_queue + self.time_queue = time_queue + self.solution_queue = solution_queue + self.newBlockEvent = multiprocessing.Event() + self.newBlockEvent.clear() + self.curr_block = curr_block + self.curr_block_num = curr_block_num + self.curr_diff = curr_diff + self.check_block = check_block + self.stopEvent = stopEvent + self.limit = limit + + def run(self): + block_number: int + block_bytes: bytes + block_difficulty: int + nonce_limit = int(math.pow(2,64)) - 1 + + # Start at random nonce + nonce_start = self.update_interval * self.proc_num + random.randint( 0, nonce_limit ) + nonce_end = nonce_start + self.update_interval + while not self.stopEvent.is_set(): + if self.newBlockEvent.is_set(): + with self.check_block: + block_number = self.curr_block_num.value + block_bytes = bytes(self.curr_block) + block_difficulty = int(self.curr_diff[0] >> 32 | self.curr_diff[1]) + + self.newBlockEvent.clear() + # reset nonces to start from random point + nonce_start = self.update_interval * self.proc_num + random.randint( 0, nonce_limit ) + nonce_end = nonce_start + self.update_interval + + # Do a block of nonces + solution, time = solve_for_nonce_block(self, nonce_start, nonce_end, block_bytes, block_difficulty, self.limit, block_number) + if solution is not None: + self.solution_queue.put(solution) + + # Send time + self.time_queue.put_nowait(time) + + nonce_start += self.update_interval * self.num_proc + nonce_end += self.update_interval * self.num_proc + + +def solve_for_nonce_block(solver: Solver, nonce_start: int, nonce_end: int, block_bytes: bytes, difficulty: int, limit: int, block_number: int) -> Tuple[Optional[POWSolution], int]: + best_local = float('inf') + best_seal_local = [0]*32 + start = time.time() + for nonce in range(nonce_start, nonce_end): + # Create seal. + nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) + pre_seal = nonce_bytes + block_bytes + seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + kec = keccak.new(digest_bits=256) + seal = kec.update( seal_sh256 ).digest() + seal_number = int.from_bytes(seal, "big") + + # Check if seal meets difficulty + product = seal_number * difficulty + if product < limit: + print(f"{solver.proc_num} found a solution: {nonce}, {block_number}, {str(block_bytes)}, {str(seal)}, {difficulty}") + # Found a solution, save it. + return POWSolution(nonce, block_number, difficulty, seal), time.time() - start + + if (product - limit) < best_local: + best_local = product - limit + best_seal_local = seal + + # Send best solution to best queue. + solver.best_queue.put((best_local, best_seal_local)) + return None, time.time() - start + + +def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, update_interval: int = 50_000 ) -> Optional[POWSolution]: """ Solves the POW for registration using multiprocessing. Args: @@ -131,7 +238,6 @@ def millify(n: int): update_interval: int Number of nonces to solve before updating block information. Note: - - We should modify the number of processes based on user input. - We can also modify the update interval to do smaller blocks of work, while still updating the block information after a different number of nonces, to increase the transparency of the process while still keeping the speed. @@ -140,117 +246,151 @@ def millify(n: int): num_processes = multiprocessing.cpu_count() if update_interval is None: - update_interval = 500_000 + update_interval = 50_000 + limit = int(math.pow(2,256)) - 1 + + console = bittensor.__console__ + status = console.status("Solving") + + best_seal: bytes + best_number: int + best_number = float('inf') + + curr_block = multiprocessing.Array('h', 64, lock=True) # byte array + curr_block_num = multiprocessing.Value('i', 0, lock=True) # int + curr_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] + + def update_curr_block(block_number: int, block_bytes: bytes, diff: int, lock: multiprocessing.Lock): + with lock: + curr_block_num.value = block_number + for i in range(64): + curr_block[i] = block_bytes[i] + curr_diff[0] = diff >> 32 + curr_diff[1] = diff & 0xFFFFFFFF # low 32 bits + + status.start() + + # Establish communication queues + stopEvent = multiprocessing.Event() + stopEvent.clear() + best_queue = multiprocessing.Queue() + solution_queue = multiprocessing.Queue() + time_queue = multiprocessing.Queue() + check_block = multiprocessing.Lock() + + # Start consumers + solvers = [ Solver(i, num_processes, update_interval, best_queue, time_queue, solution_queue, stopEvent, curr_block, curr_block_num, curr_diff, check_block, limit) + for i in range(num_processes) ] + + # Get first block block_number = subtensor.get_current_block() difficulty = subtensor.difficulty block_hash = subtensor.substrate.get_block_hash( block_number ) while block_hash == None: block_hash = subtensor.substrate.get_block_hash( block_number ) block_bytes = block_hash.encode('utf-8')[2:] - - limit = int(math.pow(2,256)) - 1 - nonce_limit = int(math.pow(2,64)) - 1 - nonce = random.randint( 0, nonce_limit ) - start_time = time.time() + old_block_number = block_number + # Set to current block + update_curr_block(block_number, block_bytes, difficulty, check_block) - console = bittensor.__console__ - status = console.status("Solving") - - #found_solution = multiprocessing.Value('q', -1, lock=False) # int - found_solution = multiprocessing.Array('Q', [0, 0, 0], lock=True) # [valid, nonce_high, nonce_low] - best_raw = struct.pack("d", float('inf')) - best = multiprocessing.Array(ctypes.c_char, best_raw, lock=True) # byte array to get around int size of ctypes - best_seal = multiprocessing.Array('h', 32, lock=True) # short array should hold bytes (0, 256) + # Set new block events for each solver to start + for w in solvers: + w.newBlockEvent.set() - with multiprocessing.Pool(processes=num_processes, initializer=initProcess_, initargs=(solve_, found_solution, best, best_seal)) as pool: - status.start() - while found_solution[0] == 0 and not wallet.is_registered(subtensor): - iterable = [( nonce_start, - nonce_start + update_interval , - block_bytes, - difficulty, - block_hash, - block_number, - limit) for nonce_start in list(range(nonce, nonce + update_interval*num_processes, update_interval))] - result = pool.starmap(solve_, iterable=iterable) - old_nonce = nonce - nonce += update_interval*num_processes - nonce = nonce % nonce_limit - itrs_per_sec = update_interval*num_processes / (time.time() - start_time) - start_time = time.time() - difficulty = subtensor.difficulty - block_number = subtensor.get_current_block() + for w in solvers: + w.start() # start the solver processes + + start_time = time.time() + solution = None + best_seal = None + itrs_per_sec = 0 + while not wallet.is_registered(subtensor): + # Wait until a solver finds a solution + try: + solution = solution_queue.get(block=True, timeout=0.25) + if solution is not None: + break + except Empty: + # No solution found, try again + pass + + # check for new block + block_number = subtensor.get_current_block() + if block_number != old_block_number: + old_block_number = block_number + # update block information block_hash = subtensor.substrate.get_block_hash( block_number) while block_hash == None: block_hash = subtensor.substrate.get_block_hash( block_number) block_bytes = block_hash.encode('utf-8')[2:] - with best_seal.get_lock(): - message = f"""Solving - time spent: {time.time() - start_time} - Difficulty: [bold white]{millify(difficulty)}[/bold white] - Iters: [bold white]{get_human_readable(int(itrs_per_sec), 'H')}/s[/bold white] - Best: [bold white]{binascii.hexlify(bytes(best_seal) or bytes(0))}[/bold white]""" - status.update(message.replace(" ", "")) - - # exited while, found_solution contains the nonce or wallet is registered - if found_solution[0] == 0: # didn't find solution - status.stop() - return None, None, None, None, None - - found_unpacked: int = found_solution[1] << 32 | found_solution[2] - nonce, block_number, block_hash, difficulty, seal = result[ math.floor( (found_unpacked-old_nonce) / update_interval) ] - status.stop() - return nonce, block_number, block_hash, difficulty, seal - -def initProcess_(f, found_solution, best, best_seal): - f.found = found_solution - f.best = best - f.best_seal = best_seal + difficulty = subtensor.difficulty -def solve_(nonce_start, nonce_end, block_bytes, difficulty, block_hash, block_number, limit): - best_local = float('inf') - best_seal_local = [0]*32 - start = time.time() - for nonce in range(nonce_start, nonce_end): - # Create seal. - nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) - pre_seal = nonce_bytes + block_bytes - seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() - kec = keccak.new(digest_bits=256) - seal = kec.update( seal_sh256 ).digest() - seal_number = int.from_bytes(seal, "big") - product = seal_number * difficulty + update_curr_block(block_number, block_bytes, difficulty, check_block) + # Set new block events for each solver + for w in solvers: + w.newBlockEvent.set() + + # Get times for each solver + time_total = 0 + num_time = 0 + while time_queue.qsize() > 0: + try: + time_ = time_queue.get_nowait() + time_total += time_ + num_time += 1 + + except Empty: + break + + if num_time > 0: + time_avg = time_total / num_time + itrs_per_sec = update_interval*num_processes / time_avg + + #times = [ time_queue.get() for _ in solvers ] + #time_avg = average(times) - if product < limit: - with solve_.found.get_lock(): - solve_.found[0] = 1; - solve_.found[1] = nonce >> 32 - solve_.found[2] = nonce & 0xFFFFFFFF # low 32 bits - return (nonce, block_number, block_hash, difficulty, seal) + - if (product - limit) < best_local: - best_local = product - limit - best_seal_local = seal + # get best solution + while best_queue.qsize() > 0: + try: + num, seal = best_queue.get_nowait() + if num < best_number: + best_number = num + best_seal = seal - with solve_.best.get_lock(): - best_value_as_d = struct.unpack('d', solve_.best.raw)[0] + except Empty: + break - if best_local < best_value_as_d: - with solve_.best_seal.get_lock(): - solve_.best.raw = struct.pack('d', best_local) - for i in range(32): - solve_.best_seal[i] = best_seal_local[i] + message = f"""Solving + time spent: {time.time() - start_time} + Difficulty: [bold white]{millify(difficulty)}[/bold white] + Iters: [bold white]{get_human_readable(int(itrs_per_sec), 'H')}/s[/bold white] + Block: [bold white]{block_number}[/bold white] + Block_hash: [bold white]{block_hash.encode('utf-8')}[/bold white] + Best: [bold white]{binascii.hexlify(bytes(best_seal) if best_seal else bytes(0))}[/bold white]""" + status.update(message.replace(" ", "")) + + # exited while, found_solution contains the nonce or wallet is registered + if solution is not None: + stopEvent.set() # stop all other processes + status.stop() + + return solution + status.stop() return None + + def create_pow( subtensor, wallet, num_processes: int = None, update_interval: int = None ) -> Optional[Dict[str, Any]]: - nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet, num_processes=num_processes, update_interval=update_interval ) + solution: POWSolution = solve_for_difficulty_fast( subtensor, wallet, num_processes=num_processes, update_interval=update_interval ) + nonce, block_number, difficulty, seal = solution.nonce, solution.block_number, solution.difficulty, solution.seal return None if nonce is None else { 'nonce': nonce, 'difficulty': difficulty, 'block_number': block_number, - 'block_hash': block_hash, 'work': binascii.hexlify(seal) } From e963b4b99e05d49d853bb6e8dd0c69c58f8c1917 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 25 Jul 2022 12:24:10 -0400 Subject: [PATCH 19/86] fix test --- tests/integration_tests/test_cli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 6ec42bff0e..5c70a3921a 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1055,6 +1055,8 @@ def test_register( self ): config = self.config config.subtensor._mock = True config.command = "register" + config.num_processes = 1 + config.update_interval = 50_000 config.subtensor.network = "mock" config.no_prompt = True From e324d9e6818eb5bb92f4e2ce00e215e182c19ca4 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 10:15:15 -0400 Subject: [PATCH 20/86] change import to cubit --- bittensor/_cli/__init__.py | 1 + bittensor/utils/register_cuda.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 448fd1cd63..0eb502cebc 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -238,6 +238,7 @@ def config() -> 'bittensor.config': help='''Register a wallet to a network.''' ) register_parser.add_argument( + '--cuda', '--cuda.use_cuda', '--subtensor.cuda.use_cuda', dest='subtensor.cuda.use_cuda', diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py index baf1fc7f04..c6f974b0dd 100644 --- a/bittensor/utils/register_cuda.py +++ b/bittensor/utils/register_cuda.py @@ -4,8 +4,8 @@ from typing import Tuple import numpy as np -from bittensor_register_cuda import reset_cuda as reset_cuda_c -from bittensor_register_cuda import solve_cuda as solve_cuda_c +from cubit import reset_cuda as reset_cuda_c +from cubit import solve_cuda as solve_cuda_c from Crypto.Hash import keccak From 22244274d2ea478fecf8a8fbaaf42f905cef8000 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 14:29:20 -0400 Subject: [PATCH 21/86] fix import and default --- bittensor/utils/__init__.py | 5 ++++- bittensor/utils/register_cuda.py | 7 +++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 96f77c0608..5f67132bfc 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -398,7 +398,7 @@ def millify(n: int): return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) -def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'bittensor.Wallet', update_interval: int = 5_000_000, TPB: int = 512, dev_id: int = 0 ) -> Optional[POWSolution]: +def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'bittensor.Wallet', update_interval: int = 50_000, TPB: int = 512, dev_id: int = 0 ) -> Optional[POWSolution]: """ Solves the registration fast using CUDA Args: @@ -415,6 +415,9 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b """ if not torch.cuda.is_available(): raise Exception("CUDA not available") + + if update_interval is None: + update_interval = 50_000 block_number = subtensor.get_current_block() difficulty = subtensor.difficulty diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py index c6f974b0dd..2d49529d73 100644 --- a/bittensor/utils/register_cuda.py +++ b/bittensor/utils/register_cuda.py @@ -4,8 +4,7 @@ from typing import Tuple import numpy as np -from cubit import reset_cuda as reset_cuda_c -from cubit import solve_cuda as solve_cuda_c +import cubit from Crypto.Hash import keccak @@ -58,7 +57,7 @@ def create_seal_hash( block_bytes:bytes, nonce:int ) -> bytes: # Call cython function # int blockSize, uint64 nonce_start, uint64 update_interval, const unsigned char[:] limit, # const unsigned char[:] block_bytes, int dev_id - solution = solve_cuda_c(TPB, nonce_start, update_interval, upper_bytes, block_bytes, dev_id) # 0 is first GPU + solution = cubit.solve_cuda(TPB, nonce_start, update_interval, upper_bytes, block_bytes, dev_id) # 0 is first GPU seal = None if solution != -1: print(f"Checking solution: {solution} for bn: {bn}") @@ -74,4 +73,4 @@ def reset_cuda(): """ Resets the CUDA environment. """ - reset_cuda_c() + cubit.reset_cuda() From d124a864cf10f434f40939c4d11fe328cb42870e Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 17:56:43 -0400 Subject: [PATCH 22/86] up default should have default in CLI call --- bittensor/_cli/cli_impl.py | 2 +- bittensor/_subtensor/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 1858fc7651..72a6a62860 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -231,7 +231,7 @@ def register( self ): wallet = wallet, prompt = not self.config.no_prompt, TPB = self.config.subtensor.cuda.get('TPB', None), - update_interval = self.config.get('update_interval', None), + update_interval = self.config.cuda.get('update_interval', None), num_processes = self.config.get('num_processes', None), cuda = self.config.subtensor.cuda.get('use_cuda', None), dev_id = self.config.subtensor.cuda.get('dev_id', None) diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index b9b8fd5901..3b2e1ee3fe 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -200,7 +200,7 @@ def add_defaults(cls, defaults ): defaults.subtensor.cuda.dev_id = 0 defaults.subtensor.cuda.use_cuda = False - defaults.subtensor.cuda.update_interval = 10_000 + defaults.subtensor.cuda.update_interval = 50_000 defaults.subtensor.cuda.TPB = 256 @staticmethod From 7f7c04361cc715f4c5a3c27bc94ae48370a59839 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 17:56:52 -0400 Subject: [PATCH 23/86] add comments about params --- bittensor/_subtensor/subtensor_impl.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index af229c8c77..751813b662 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -459,6 +459,18 @@ def register ( or returns false if the extrinsic fails to be finalized within the timeout. prompt (bool): If true, the call waits for confirmation from the user before proceeding. + max_allowed_attempts (int): + Maximum number of attempts to register the wallet. + cuda (bool): + If true, the wallet should be registered on the cuda device. + dev_id (int): + The cuda device id. + TPB (int): + The number of threads per block (cuda). + num_processes (int): + The number of processes to use to register. + update_interval (int): + The number of nonces to solve between updates. Returns: success (bool): flag is true if extrinsic was finalized or uncluded in the block. From 91e613e6d467df67fd8367f081f91627551cc974 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 20:56:21 -0400 Subject: [PATCH 24/86] fix config var access --- bittensor/_cli/cli_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 72a6a62860..fe40cf4d07 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -231,7 +231,7 @@ def register( self ): wallet = wallet, prompt = not self.config.no_prompt, TPB = self.config.subtensor.cuda.get('TPB', None), - update_interval = self.config.cuda.get('update_interval', None), + update_interval = self.config.subtensor.cuda.get('update_interval', None), num_processes = self.config.get('num_processes', None), cuda = self.config.subtensor.cuda.get('use_cuda', None), dev_id = self.config.subtensor.cuda.get('dev_id', None) From be3a7af9c244203d459df7d0c3cbd733aa8636b0 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 4 Aug 2022 20:56:47 -0400 Subject: [PATCH 25/86] add cubit as extra --- bittensor/utils/register_cuda.py | 15 +++++++++++++-- setup.py | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py index 2d49529d73..f64f4777b4 100644 --- a/bittensor/utils/register_cuda.py +++ b/bittensor/utils/register_cuda.py @@ -4,7 +4,6 @@ from typing import Tuple import numpy as np -import cubit from Crypto.Hash import keccak @@ -30,7 +29,14 @@ def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block Tuple[int64, bytes] Tuple of the nonce and the seal corresponding to the solution. Returns -1 for nonce if no solution is found. - """ + """ + + try: + import cubit + except ImportError: + raise ImportError("Please install cubit") + + upper = int(limit // difficulty) upper_bytes = upper.to_bytes(32, byteorder='little', signed=False) @@ -73,4 +79,9 @@ def reset_cuda(): """ Resets the CUDA environment. """ + try: + import cubit + except ImportError: + raise ImportError("Please install cubit") + cubit.reset_cuda() diff --git a/setup.py b/setup.py index 4168052160..d4a9723bcd 100644 --- a/setup.py +++ b/setup.py @@ -69,4 +69,7 @@ 'Topic :: Software Development :: Libraries :: Python Modules', ], python_requires='>=3.7', + extras_requires={ + 'cubit': ['cubit>=1.0.5 @ git+https://github.com/opentensor/cubit.git'] + } ) From 42e528a5322ba75d6e9d2268a3de6864e4fefeb4 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 9 Aug 2022 18:01:08 -0400 Subject: [PATCH 26/86] handle stale pow differently check registration after failure --- bittensor/_subtensor/subtensor_impl.py | 74 +++++++++++++++----------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 751813b662..64f5de3a8c 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -511,47 +511,57 @@ def register ( # pow successful, proceed to submit pow to chain for registration else: #check if pow result is still valid - while pow_result['block_number'] >= self.get_current_block() - 3: - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module='SubtensorModule', - call_function='register', - call_params={ - 'block_number': pow_result['block_number'], - 'nonce': pow_result['nonce'], - 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), - 'hotkey': wallet.hotkey.ss58_address, - 'coldkey': wallet.coldkeypub.ss58_address - } - ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) - response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='register', + call_params={ + 'block_number': pow_result['block_number'], + 'nonce': pow_result['nonce'], + 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), + 'hotkey': wallet.hotkey.ss58_address, + 'coldkey': wallet.coldkeypub.ss58_address + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + time.sleep(1) + neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) + if not neuron.is_null: + # already registered return True - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) - time.sleep(1) - continue - - # Successful registration, final check for neuron and pubkey - else: - bittensor.__console__.print(":satellite: Checking Balance...") - neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) + continue + + # Successful registration, final check for neuron and pubkey + else: + bittensor.__console__.print(":satellite: Checking Balance...") + neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) + if not neuron.is_null: bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") return True + else: + # neuron not found, try again + return False #Failed registration, retry pow attempts += 1 if attempts > max_allowed_attempts: bittensor.__console__.print( "[red]No more attempts.[/red]" ) return False + elif pow_result['block_number'] < self.get_current_block() - 3: + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + return False else: status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) continue From 4288c3af92927fc60bc93379d562d684e2eaf9ce Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 14:56:05 -0400 Subject: [PATCH 27/86] [feature] cpu register faster (#854) * add update interval and num proc flag * add better number output * optimize multiproc cpu reg keeping proc until solution * fix test * make sure to exit properly if registered during * fix tests * change import to use tests * add optional type hints and None default * change to count using allowed processes * add documentation. Fix random start --- bittensor/_cli/__init__.py | 18 + bittensor/_cli/cli_impl.py | 2 +- bittensor/_subtensor/subtensor_impl.py | 9 +- bittensor/utils/__init__.py | 406 +++++++++++++----- tests/integration_tests/test_cli.py | 4 +- .../bittensor_tests/test_balance.py | 2 +- .../bittensor_tests/utils/test_utils.py | 33 +- 7 files changed, 346 insertions(+), 128 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index e74946b58e..83fe2b8f26 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -571,6 +571,24 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) + register_parser.add_argument( + '--num_processes', + '--num', + '-n', + dest='num_processes', + help="Number of processors to use for registration", + type=int, + default=None, + ) + register_parser.add_argument( + '--update_interval', + '-u', + dest='update_interval', + help="The number of nonces to process before checking for next block during registration", + type=int, + default=None, + ) + bittensor.wallet.add_args( register_parser ) bittensor.subtensor.add_args( register_parser ) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index cec4734fd4..245b943885 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -239,7 +239,7 @@ def register( self ): """ wallet = bittensor.wallet( config = self.config ) subtensor = bittensor.subtensor( config = self.config ) - subtensor.register( wallet = wallet, prompt = not self.config.no_prompt) + subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, num_processes = self.config.num_processes, update_interval = self.config.update_interval ) def transfer( self ): r""" Transfer token of amount to destination. diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index e1f3dc1b33..e71fcca7fa 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -16,8 +16,7 @@ # DEALINGS IN THE SOFTWARE. import torch from rich.prompt import Confirm -from typing import List, Dict, Union -from multiprocessing import Process +from typing import List, Dict, Union, Optional import bittensor from tqdm import tqdm @@ -440,7 +439,9 @@ def register ( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, - max_allowed_attempts: int = 3 + max_allowed_attempts: int = 3, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, ) -> bool: r""" Registers the wallet to chain. Args: @@ -474,7 +475,7 @@ def register ( attempts = 1 while True: # Solve latest POW. - pow_result = bittensor.utils.create_pow( self, wallet ) + pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval ) with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: # pow failed diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 8ebdb9ef95..a5379e5428 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,21 +1,22 @@ import binascii -import multiprocessing -import ctypes -import struct import hashlib -from Crypto.Hash import keccak import math -import bittensor +import multiprocessing +import numbers +import os import random -import rich import time -import torch -import numbers +from dataclasses import dataclass +from queue import Empty +from typing import Any, Dict, Optional, Tuple, Union + +import bittensor import pandas import requests +import torch +from Crypto.Hash import keccak +from substrateinterface import Keypair from substrateinterface.utils import ss58 -from substrateinterface import Keypair, KeypairType -from typing import Any, Tuple, List, Union, Optional def indexed_values_to_dataframe ( @@ -76,10 +77,12 @@ def u8_list_to_hex( values: list ): return total def create_seal_hash( block_hash:bytes, nonce:int ) -> bytes: - nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) block_bytes = block_hash.encode('utf-8')[2:] + nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) pre_seal = nonce_bytes + block_bytes - seal = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + kec = keccak.new(digest_bits=256) + seal = kec.update( seal_sh256 ).digest() return seal def seal_meets_difficulty( seal:bytes, difficulty:int ): @@ -102,7 +105,172 @@ def solve_for_difficulty( block_hash, difficulty ): break return nonce, seal -def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, update_interval: int = 500000 ) -> Tuple[int, int, Any, int, Any]: + +def get_human_readable(num, suffix="H"): + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: + if abs(num) < 1000.0: + return f"{num:3.1f}{unit}{suffix}" + num /= 1000.0 + return f"{num:.1f}Y{suffix}" + + +def millify(n: int): + millnames = ['',' K',' M',' B',' T'] + n = float(n) + millidx = max(0,min(len(millnames)-1, + int(math.floor(0 if n == 0 else math.log10(abs(n))/3)))) + + return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) + +@dataclass +class POWSolution: + """A solution to the registration PoW problem.""" + nonce: int + block_number: int + difficulty: int + seal: bytes + +class Solver(multiprocessing.Process): + """ + A process that solves the registration PoW problem. + + Args: + proc_num: int + The number of the process being created. + num_proc: int + The total number of processes running. + update_interval: int + The number of nonces to try to solve before checking for a new block. + best_queue: multiprocessing.Queue + The queue to put the best nonce the process has found during the pow solve. + New nonces are added each update_interval. + time_queue: multiprocessing.Queue + The queue to put the time the process took to finish each update_interval. + Used for calculating the average time per update_interval across all processes. + solution_queue: multiprocessing.Queue + The queue to put the solution the process has found during the pow solve. + newBlockEvent: multiprocessing.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. + stopEvent: multiprocessing.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. + curr_block: multiprocessing.Array + 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. + curr_block_num: multiprocessing.Value + 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 newBlockEvent is set. + curr_diff: multiprocessing.Array + 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. + check_block: multiprocessing.Lock + The lock to prevent this process from getting the new block data while the main process is updating the data. + limit: int + The limit of the pow solve for a valid solution. + """ + proc_num: int + num_proc: int + update_interval: int + best_queue: multiprocessing.Queue + time_queue: multiprocessing.Queue + solution_queue: multiprocessing.Queue + newBlockEvent: multiprocessing.Event + stopEvent: multiprocessing.Event + curr_block: multiprocessing.Array + curr_block_num: multiprocessing.Value + curr_diff: multiprocessing.Array + check_block: multiprocessing.Lock + limit: int + + def __init__(self, proc_num, num_proc, update_interval, best_queue, time_queue, solution_queue, stopEvent, curr_block, curr_block_num, curr_diff, check_block, limit): + multiprocessing.Process.__init__(self) + self.proc_num = proc_num + self.num_proc = num_proc + self.update_interval = update_interval + self.best_queue = best_queue + self.time_queue = time_queue + self.solution_queue = solution_queue + self.newBlockEvent = multiprocessing.Event() + self.newBlockEvent.clear() + self.curr_block = curr_block + self.curr_block_num = curr_block_num + self.curr_diff = curr_diff + self.check_block = check_block + self.stopEvent = stopEvent + self.limit = limit + + def run(self): + block_number: int + block_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.stopEvent.is_set(): + if self.newBlockEvent.is_set(): + with self.check_block: + block_number = self.curr_block_num.value + block_bytes = bytes(self.curr_block) + block_difficulty = int(self.curr_diff[0] >> 32 | self.curr_diff[1]) + + self.newBlockEvent.clear() + # reset nonces to start from random point + # prevents the same nonces (for each block) from being tried by multiple processes + # also prevents the same nonces from being tried by multiple peers + nonce_start = random.randint( 0, nonce_limit ) + nonce_end = nonce_start + self.update_interval + + # Do a block of nonces + solution, time = solve_for_nonce_block(self, nonce_start, nonce_end, block_bytes, block_difficulty, self.limit, block_number) + if solution is not None: + self.solution_queue.put(solution) + + # Send time + self.time_queue.put_nowait(time) + + nonce_start += self.update_interval * self.num_proc + nonce_end += self.update_interval * self.num_proc + + +def solve_for_nonce_block(solver: Solver, nonce_start: int, nonce_end: int, block_bytes: bytes, difficulty: int, limit: int, block_number: int) -> Tuple[Optional[POWSolution], int]: + best_local = float('inf') + best_seal_local = [0]*32 + start = time.time() + for nonce in range(nonce_start, nonce_end): + # Create seal. + nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) + pre_seal = nonce_bytes + block_bytes + seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + kec = keccak.new(digest_bits=256) + seal = kec.update( seal_sh256 ).digest() + seal_number = int.from_bytes(seal, "big") + + # Check if seal meets difficulty + product = seal_number * difficulty + if product < limit: + print(f"{solver.proc_num} found a solution: {nonce}, {block_number}, {str(block_bytes)}, {str(seal)}, {difficulty}") + # Found a solution, save it. + return POWSolution(nonce, block_number, difficulty, seal), time.time() - start + + if (product - limit) < best_local: + best_local = product - limit + best_seal_local = seal + + # Send best solution to best queue. + solver.best_queue.put((best_local, best_seal_local)) + return None, time.time() - start + + +def solve_for_difficulty_fast( subtensor, wallet, num_processes: Optional[int] = None, update_interval: Optional[int] = None ) -> Optional[POWSolution]: """ Solves the POW for registration using multiprocessing. Args: @@ -115,126 +283,152 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: int = None, upd update_interval: int Number of nonces to solve before updating block information. Note: - - We should modify the number of processes based on user input. - We can also modify the update interval to do smaller blocks of work, while still updating the block information after a different number of nonces, to increase the transparency of the process while still keeping the speed. """ if num_processes == None: - num_processes = multiprocessing.cpu_count() + # get the number of allowed processes for this process + num_processes = len(os.sched_getaffinity(0)) + + if update_interval is None: + update_interval = 50_000 + limit = int(math.pow(2,256)) - 1 + + console = bittensor.__console__ + status = console.status("Solving") + + best_seal: bytes + best_number: int + best_number = float('inf') + + curr_block = multiprocessing.Array('h', 64, lock=True) # byte array + curr_block_num = multiprocessing.Value('i', 0, lock=True) # int + curr_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] + + def update_curr_block(block_number: int, block_bytes: bytes, diff: int, lock: multiprocessing.Lock): + with lock: + curr_block_num.value = block_number + for i in range(64): + curr_block[i] = block_bytes[i] + curr_diff[0] = diff >> 32 + curr_diff[1] = diff & 0xFFFFFFFF # low 32 bits + + status.start() + + # Establish communication queues + ## See the Solver class for more information on the queues. + stopEvent = multiprocessing.Event() + stopEvent.clear() + best_queue = multiprocessing.Queue() + solution_queue = multiprocessing.Queue() + time_queue = multiprocessing.Queue() + check_block = multiprocessing.Lock() + + # Start consumers + solvers = [ Solver(i, num_processes, update_interval, best_queue, time_queue, solution_queue, stopEvent, curr_block, curr_block_num, curr_diff, check_block, limit) + for i in range(num_processes) ] + + # Get first block block_number = subtensor.get_current_block() difficulty = subtensor.difficulty block_hash = subtensor.substrate.get_block_hash( block_number ) while block_hash == None: block_hash = subtensor.substrate.get_block_hash( block_number ) block_bytes = block_hash.encode('utf-8')[2:] - - limit = int(math.pow(2,256)) - 1 - nonce_limit = int(math.pow(2,64)) - 1 - nonce = random.randint( 0, nonce_limit ) - start_time = time.time() - - console = bittensor.__console__ - status = console.status("Solving") + old_block_number = block_number + # Set to current block + update_curr_block(block_number, block_bytes, difficulty, check_block) - #found_solution = multiprocessing.Value('q', -1, lock=False) # int - found_solution = multiprocessing.Array('Q', [0, 0, 0], lock=True) # [valid, nonce_high, nonce_low] - best_raw = struct.pack("d", float('inf')) - best = multiprocessing.Array(ctypes.c_char, best_raw, lock=True) # byte array to get around int size of ctypes - best_seal = multiprocessing.Array('h', 32, lock=True) # short array should hold bytes (0, 256) + # Set new block events for each solver to start + for w in solvers: + w.newBlockEvent.set() - with multiprocessing.Pool(processes=num_processes, initializer=initProcess_, initargs=(solve_, found_solution, best, best_seal)) as pool: - status.start() - while found_solution[0] == 0 and not wallet.is_registered(subtensor): - iterable = [( nonce_start, - nonce_start + update_interval , - block_bytes, - difficulty, - block_hash, - block_number, - limit) for nonce_start in list(range(nonce, nonce + update_interval*num_processes, update_interval))] - result = pool.starmap(solve_, iterable=iterable) - old_nonce = nonce - nonce += update_interval*num_processes - nonce = nonce % nonce_limit - itrs_per_sec = update_interval*num_processes / (time.time() - start_time) - start_time = time.time() - difficulty = subtensor.difficulty - block_number = subtensor.get_current_block() + for w in solvers: + w.start() # start the solver processes + + start_time = time.time() + solution = None + best_seal = None + itrs_per_sec = 0 + while not wallet.is_registered(subtensor): + # Wait until a solver finds a solution + try: + solution = solution_queue.get(block=True, timeout=0.25) + if solution is not None: + break + except Empty: + # No solution found, try again + pass + + # check for new block + block_number = subtensor.get_current_block() + if block_number != old_block_number: + old_block_number = block_number + # update block information block_hash = subtensor.substrate.get_block_hash( block_number) while block_hash == None: block_hash = subtensor.substrate.get_block_hash( block_number) block_bytes = block_hash.encode('utf-8')[2:] - with best_seal.get_lock(): - message = f"""Solving - time spent: {time.time() - start_time} - Nonce: [bold white]{nonce}[/bold white] - Difficulty: [bold white]{difficulty}[/bold white] - Iters: [bold white]{int(itrs_per_sec)}/s[/bold white] - Block: [bold white]{block_number}[/bold white] - Block_hash: [bold white]{block_hash.encode('utf-8')}[/bold white] - Best: [bold white]{binascii.hexlify(bytes(best_seal) or bytes(0))}[/bold white]""" - status.update(message.replace(" ", "")) + difficulty = subtensor.difficulty + + update_curr_block(block_number, block_bytes, difficulty, check_block) + # Set new block events for each solver + for w in solvers: + w.newBlockEvent.set() + + # Get times for each solver + time_total = 0 + num_time = 0 + while time_queue.qsize() > 0: + try: + time_ = time_queue.get_nowait() + time_total += time_ + num_time += 1 + + except Empty: + break - # exited while, found_solution contains the nonce or wallet is registered - if found_solution[0] == 0: # didn't find solution - status.stop() - return None, None, None, None, None + # Calculate average time per solver for the update_interval + if num_time > 0: + time_avg = time_total / num_time + itrs_per_sec = update_interval*num_processes / time_avg + + # get best solution from each solver using the best_queue + while best_queue.qsize() > 0: + try: + num, seal = best_queue.get_nowait() + if num < best_number: + best_number = num + best_seal = seal + + except Empty: + break - found_unpacked: int = found_solution[1] << 32 | found_solution[2] - nonce, block_number, block_hash, difficulty, seal = result[ math.floor( (found_unpacked-old_nonce) / update_interval) ] - status.stop() - return nonce, block_number, block_hash, difficulty, seal - -def initProcess_(f, found_solution, best, best_seal): - f.found = found_solution - f.best = best - f.best_seal = best_seal - -def solve_(nonce_start, nonce_end, block_bytes, difficulty, block_hash, block_number, limit): - best_local = float('inf') - best_seal_local = [0]*32 - start = time.time() - for nonce in range(nonce_start, nonce_end): - # Create seal. - nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) - pre_seal = nonce_bytes + block_bytes - seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() - kec = keccak.new(digest_bits=256) - seal = kec.update( seal_sh256 ).digest() - seal_number = int.from_bytes(seal, "big") - product = seal_number * difficulty - - if product < limit: - with solve_.found.get_lock(): - solve_.found[0] = 1; - solve_.found[1] = nonce >> 32 - solve_.found[2] = nonce & 0xFFFFFFFF # low 32 bits - return (nonce, block_number, block_hash, difficulty, seal) - - if (product - limit) < best_local: - best_local = product - limit - best_seal_local = seal + message = f"""Solving + time spent: {time.time() - start_time} + Difficulty: [bold white]{millify(difficulty)}[/bold white] + Iters: [bold white]{get_human_readable(int(itrs_per_sec), 'H')}/s[/bold white] + Block: [bold white]{block_number}[/bold white] + Block_hash: [bold white]{block_hash.encode('utf-8')}[/bold white] + Best: [bold white]{binascii.hexlify(bytes(best_seal) if best_seal else bytes(0))}[/bold white]""" + status.update(message.replace(" ", "")) + + # exited while, solution contains the nonce or wallet is registered + stopEvent.set() # stop all other processes + status.stop() - with solve_.best.get_lock(): - best_value_as_d = struct.unpack('d', solve_.best.raw)[0] - - if best_local < best_value_as_d: - with solve_.best_seal.get_lock(): - solve_.best.raw = struct.pack('d', best_local) - for i in range(32): - solve_.best_seal[i] = best_seal_local[i] + return solution - return None -def create_pow( subtensor, wallet ): - nonce, block_number, block_hash, difficulty, seal = solve_for_difficulty_fast( subtensor, wallet ) +def create_pow( subtensor, wallet, num_processes: Optional[int] = None, update_interval: Optional[int] = None ) -> Optional[Dict[str, Any]]: + solution: POWSolution = solve_for_difficulty_fast( subtensor, wallet, num_processes=num_processes, update_interval=update_interval ) + nonce, block_number, difficulty, seal = solution.nonce, solution.block_number, solution.difficulty, solution.seal return None if nonce is None else { 'nonce': nonce, 'difficulty': difficulty, 'block_number': block_number, - 'block_hash': block_hash, 'work': binascii.hexlify(seal) } diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 6c147338b3..77269d366b 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -28,7 +28,7 @@ from substrateinterface.base import Keypair from substrateinterface.exceptions import SubstrateRequestException -from ..helpers import CLOSE_IN_VALUE +from tests.helpers import CLOSE_IN_VALUE class TestCli(unittest.TestCase): @@ -1082,6 +1082,8 @@ def test_register( self ): config = self.config config.subtensor._mock = True config.command = "register" + config.num_processes = 1 + config.update_interval = 50_000 config.subtensor.network = "mock" config.no_prompt = True diff --git a/tests/unit_tests/bittensor_tests/test_balance.py b/tests/unit_tests/bittensor_tests/test_balance.py index 0349eb678f..60f61fac67 100644 --- a/tests/unit_tests/bittensor_tests/test_balance.py +++ b/tests/unit_tests/bittensor_tests/test_balance.py @@ -19,7 +19,7 @@ from typing import Union from bittensor import Balance -from ...helpers import CLOSE_IN_VALUE +from tests.helpers import CLOSE_IN_VALUE from hypothesis import given from hypothesis import strategies as st diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 677cb87ddc..03065ccdca 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -111,7 +111,8 @@ def test_u8_list_to_hex(): def test_create_seal_hash(): block_hash = '0xba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279' nonce = 10 - assert bittensor.utils.create_seal_hash(block_hash, nonce) == b'\xf5\xad\xd3\xff\x9d\xd0=F\x1c\xe0}\n9Szs[kb\xb7^@\xe94\x99hw\xa7Q\xde\x8d3' + seal_hash = bittensor.utils.create_seal_hash(block_hash, nonce) + assert seal_hash == b'\xc5\x01B6"\xa8\xa5FDPK\xe49\xad\xdat\xbb:\x87d\x13/\x86\xc6:I8\x9b\x88\xf0\xc20' def test_seal_meets_difficulty(): block_hash = '0xba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279' @@ -134,11 +135,11 @@ def test_solve_for_difficulty(): nonce, seal = bittensor.utils.solve_for_difficulty(block_hash, 1) assert nonce == 0 - assert seal == b'\xd6mKj\xd0\x00=?2<\xaa\xc6\xcf;\xfc1\xe9\x02\xaa\x8e\xa0Q\x16\x16U]\xfe\xaa\x18jU\xcd' + assert seal == b'\xe2d\xbc\x10Tu|\xd0nQ\x1f\x15wTd\xb0\x18\x8f\xc7\xe7:\x12\xc6>\\\xbe\xac\xc5/v\xa7\xce' nonce, seal = bittensor.utils.solve_for_difficulty(block_hash, 10) assert nonce == 2 - assert seal == b'\x8a\xa5\x0fA\xb1\n\xd0\xdea\x7f\x86rWq1\xa5|\x18\xd0\xc7\x81\xf4\x81\x03\xf9P\xc8\x19\xb9\x1f-\xcf' + assert seal == b'\x19\xf2H1mB3\xa3y\xda\xe7)\xc7P\x93t\xe5o\xbc$\x14sQ\x10\xc3M\xc6\x90M8vq' def test_solve_for_difficulty_fast(): block_hash = '0xba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279' @@ -149,19 +150,22 @@ def test_solve_for_difficulty_fast(): subtensor.substrate.get_block_hash = MagicMock( return_value=block_hash ) wallet = MagicMock() wallet.is_registered = MagicMock( return_value=False ) + num_proc: int = 1 - _, _, _, _, seal = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet ) + solution = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet, num_processes=num_proc ) + seal = solution.seal assert bittensor.utils.seal_meets_difficulty(seal, 1) subtensor.difficulty = 10 - _, _, _, _, seal = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet ) + solution = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet, num_processes=num_proc ) + seal = solution.seal assert bittensor.utils.seal_meets_difficulty(seal, 10) def test_solve_for_difficulty_fast_registered_already(): # tests if the registration stops after the first block of nonces for _ in range(10): - workblocks_before_is_registered = random.randint(2, 10) + workblocks_before_is_registered = random.randint(1, 4) # return False each work block but return True after a random number of blocks is_registered_return_values = [False for _ in range(workblocks_before_is_registered)] + [True] + [False, False] @@ -175,12 +179,9 @@ def test_solve_for_difficulty_fast_registered_already(): wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) # all arugments should return None to indicate an early return - a, b, c, d, e = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet, num_processes = 1, update_interval = 1000) - assert a is None - assert b is None - assert c is None - assert d is None - assert e is None + solution = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet, num_processes = 1, update_interval = 1000) + + assert solution is None # called every time until True assert wallet.is_registered.call_count == workblocks_before_is_registered + 1 @@ -193,13 +194,15 @@ def test_solve_for_difficulty_fast_missing_hash(): subtensor.substrate.get_block_hash = MagicMock( side_effect= [None, None] + [block_hash]*20) wallet = MagicMock() wallet.is_registered = MagicMock( return_value=False ) + num_proc: int = 1 - _, _, _, _, seal = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet ) - + solution = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet, num_processes=num_proc ) + seal = solution.seal assert bittensor.utils.seal_meets_difficulty(seal, 1) subtensor.difficulty = 10 - _, _, _, _, seal = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet ) + solution = bittensor.utils.solve_for_difficulty_fast( subtensor, wallet, num_processes=num_proc ) + seal = solution.seal assert bittensor.utils.seal_meets_difficulty(seal, 10) def test_is_valid_ss58_address(): From 1c49e4c46971df096233d34da712f71b602adcc9 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 17:09:04 -0400 Subject: [PATCH 28/86] restrict number of processes for integration test --- tests/integration_tests/test_subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index 2d411f1fc0..b81408920d 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -411,7 +411,7 @@ def process_events(self): self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) # should return True - assert self.subtensor.register(wallet=wallet,) + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True # calls until True and once again before exiting subtensor class # This assertion is currently broken when difficulty is too low #assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 @@ -448,7 +448,7 @@ def process_events(self): self.subtensor.substrate.submit_extrinsic = MagicMock(side_effect = submit_extrinsic) # should return True - assert self.subtensor.register(wallet=wallet,) == True + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True def test_registration_failed( self ): class failed(): From 34a82e0f5e5c5de1367bb45a64b9d55bb2395222 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 17:30:30 -0400 Subject: [PATCH 29/86] fix stale check --- bittensor/_subtensor/subtensor_impl.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 9e2de8e58d..50123823ea 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -511,6 +511,10 @@ def register ( # pow successful, proceed to submit pow to chain for registration else: #check if pow result is still valid + if pow_result['block_number'] < self.get_current_block() - 3: + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + continue + with self.substrate as substrate: # create extrinsic call call = substrate.compose_call( @@ -559,9 +563,6 @@ def register ( if attempts > max_allowed_attempts: bittensor.__console__.print( "[red]No more attempts.[/red]" ) return False - elif pow_result['block_number'] < self.get_current_block() - 3: - bittensor.__console__.print( "[red]POW is stale.[/red]" ) - return False else: status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) continue From bfb1662a7d0c5ccb5204bdbe6bc9db539a30957d Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 18:09:39 -0400 Subject: [PATCH 30/86] use wallet.is_registered instead --- bittensor/_subtensor/subtensor_impl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 50123823ea..4503f3cb71 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -542,7 +542,7 @@ def register ( bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) time.sleep(1) neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) - if not neuron.is_null: + if wallet.is_registered( self ): # already registered return True continue @@ -551,7 +551,7 @@ def register ( else: bittensor.__console__.print(":satellite: Checking Balance...") neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) - if not neuron.is_null: + if wallet.is_registered( self ): bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") return True else: From 52635741ca2f1f93c027f259ae7fd8e7de507d90 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 18:19:03 -0400 Subject: [PATCH 31/86] attempt to fix test issue --- bittensor/_dataset/dataset_impl.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bittensor/_dataset/dataset_impl.py b/bittensor/_dataset/dataset_impl.py index f86f0c7aff..7bede7b666 100644 --- a/bittensor/_dataset/dataset_impl.py +++ b/bittensor/_dataset/dataset_impl.py @@ -160,8 +160,7 @@ def __init__( self.build_hash_table() - if not os.path.isdir(os.path.expanduser(data_dir)): - os.makedirs(os.path.expanduser(data_dir)) + os.path.expanduser(data_dir, exits_ok=True) self.data_queue = ThreadQueue( producer_target = self.reserve_multiple_data, From c6b2ca9cc3aa48aa9bd82884c2ca7108d0784bf6 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 18:26:46 -0400 Subject: [PATCH 32/86] fix my test --- tests/integration_tests/test_subtensor.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index b81408920d..d126f781d7 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -402,19 +402,21 @@ def process_events(self): mock_neuron.is_null = True with patch('bittensor.Subtensor.difficulty'): + with patch('multiprocessing.Queue.get') as mock_queue_get: + mock_queue_get.return_value = None - wallet = bittensor.wallet(_mock=True) - wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) + wallet = bittensor.wallet(_mock=True) + wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) - self.subtensor.difficulty= 1 - self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) - self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) + self.subtensor.difficulty= 1 + self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) + self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) - # should return True - assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True - # calls until True and once again before exiting subtensor class - # This assertion is currently broken when difficulty is too low - #assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 + # should return True + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True + # calls until True and once again before exiting subtensor class + # This assertion is currently broken when difficulty is too low + assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 def test_registration_partly_failed( self ): From 103549870d23b82ed0637711f2392e3c86eed2b9 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 18:31:14 -0400 Subject: [PATCH 33/86] oops typo --- bittensor/_dataset/dataset_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_dataset/dataset_impl.py b/bittensor/_dataset/dataset_impl.py index 7bede7b666..a60a25781d 100644 --- a/bittensor/_dataset/dataset_impl.py +++ b/bittensor/_dataset/dataset_impl.py @@ -160,7 +160,7 @@ def __init__( self.build_hash_table() - os.path.expanduser(data_dir, exits_ok=True) + os.path.expanduser(data_dir, exists_ok=True) self.data_queue = ThreadQueue( producer_target = self.reserve_multiple_data, From d38c6d3f81dfd22e3ec583c67e41ace7368b98b6 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 18:47:56 -0400 Subject: [PATCH 34/86] typo again ugh --- bittensor/_dataset/dataset_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_dataset/dataset_impl.py b/bittensor/_dataset/dataset_impl.py index a60a25781d..f104b632bf 100644 --- a/bittensor/_dataset/dataset_impl.py +++ b/bittensor/_dataset/dataset_impl.py @@ -160,7 +160,7 @@ def __init__( self.build_hash_table() - os.path.expanduser(data_dir, exists_ok=True) + os.makedirs(os.path.expanduser(data_dir), exist_ok=True) self.data_queue = ThreadQueue( producer_target = self.reserve_multiple_data, From e9845ceaa74dc898dca1fabd027331027fa5d9d3 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 18:49:38 -0400 Subject: [PATCH 35/86] remove print out --- bittensor/utils/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 4c4326c02d..58d2d00df3 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -260,7 +260,6 @@ def solve_for_nonce_block(solver: Solver, nonce_start: int, nonce_end: int, bloc # Check if seal meets difficulty product = seal_number * difficulty if product < limit: - print(f"{solver.proc_num} found a solution: {nonce}, {block_number}, {str(block_bytes)}, {str(seal)}, {difficulty}") # Found a solution, save it. return POWSolution(nonce, block_number, difficulty, seal), time.time() - start From 010f859f3d19c5eeb862fcc048badd42228752ce Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 18:59:50 -0400 Subject: [PATCH 36/86] fix partly reg test --- bittensor/_subtensor/subtensor_impl.py | 11 ++++++----- tests/integration_tests/test_subtensor.py | 15 +++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 4503f3cb71..d6af166a1a 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -539,18 +539,19 @@ def register ( # process if registration successful, try again if pow is still valid response.process_events() if not response.is_success: + if 'key is already registered' in response.error_message: + # Error meant that the key is already registered. + bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") + return True + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) time.sleep(1) neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) - if wallet.is_registered( self ): - # already registered - return True continue # Successful registration, final check for neuron and pubkey else: bittensor.__console__.print(":satellite: Checking Balance...") - neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) if wallet.is_registered( self ): bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") return True @@ -562,7 +563,7 @@ def register ( attempts += 1 if attempts > max_allowed_attempts: bittensor.__console__.print( "[red]No more attempts.[/red]" ) - return False + return False else: status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) continue diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index d126f781d7..f34b7476cf 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -433,21 +433,24 @@ def __init__(self): def process_events(self): return True - is_registered_return_values = [False for _ in range(100)] - submit_extrinsic = [failed(), failed(), success()] + submit_extrinsic_mock = MagicMock( side_effect = [failed(), failed(), success()]) + + def is_registered_side_effect(*args, **kwargs): + nonlocal submit_extrinsic_mock + return submit_extrinsic_mock.call_count < 3 + current_block = [i for i in range(0,100)] mock_neuron = MagicMock() mock_neuron.is_null = True with patch('bittensor.Subtensor.difficulty'): wallet = bittensor.wallet(_mock=True) - wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) - + wallet.is_registered = MagicMock( side_effect=is_registered_side_effect ) - self.subtensor.difficulty= 1 + self.subtensor.difficulty = 1 self.subtensor.get_current_block = MagicMock(side_effect=current_block) self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) - self.subtensor.substrate.submit_extrinsic = MagicMock(side_effect = submit_extrinsic) + self.subtensor.substrate.submit_extrinsic = submit_extrinsic_mock # should return True assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True From 6a95ada9cf96422b350334759bf06d2a14ad9537 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 19:12:18 -0400 Subject: [PATCH 37/86] fix if solution None --- bittensor/utils/__init__.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 58d2d00df3..b11a4edc42 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -535,12 +535,11 @@ def create_pow( subtensor, wallet, cuda: bool = False, dev_id: int = 0, tpb: int else: solution: POWSolution = solve_for_difficulty_fast( subtensor, wallet, num_processes=num_processes, update_interval=update_interval ) - nonce, block_number, difficulty, seal = solution.nonce, solution.block_number, solution.difficulty, solution.seal - return None if nonce is None else { - 'nonce': nonce, - 'difficulty': difficulty, - 'block_number': block_number, - 'work': binascii.hexlify(seal) + return None if solution is None else { + 'nonce': solution.nonce, + 'difficulty': solution.difficulty, + 'block_number': solution.block_number, + 'work': binascii.hexlify(solution.seal) } def version_checking(): From 378b241798b35103a9666bf78e0a096a23980b38 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 19:18:04 -0400 Subject: [PATCH 38/86] fix test? --- bittensor/_subtensor/subtensor_impl.py | 3 +-- tests/integration_tests/test_subtensor.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index d6af166a1a..c58126facd 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -546,8 +546,6 @@ def register ( bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) time.sleep(1) - neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) - continue # Successful registration, final check for neuron and pubkey else: @@ -557,6 +555,7 @@ def register ( return True else: # neuron not found, try again + bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") return False #Failed registration, retry pow diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index f34b7476cf..e319a52ad9 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -440,8 +440,6 @@ def is_registered_side_effect(*args, **kwargs): return submit_extrinsic_mock.call_count < 3 current_block = [i for i in range(0,100)] - mock_neuron = MagicMock() - mock_neuron.is_null = True with patch('bittensor.Subtensor.difficulty'): wallet = bittensor.wallet(_mock=True) @@ -449,7 +447,6 @@ def is_registered_side_effect(*args, **kwargs): self.subtensor.difficulty = 1 self.subtensor.get_current_block = MagicMock(side_effect=current_block) - self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) self.subtensor.substrate.submit_extrinsic = submit_extrinsic_mock # should return True From 47ef70c774a9d82212ebb47a45d42385b12f4308 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Thu, 11 Aug 2022 19:19:50 -0400 Subject: [PATCH 39/86] fix patch --- tests/integration_tests/test_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index e319a52ad9..99b6a75cc4 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -402,7 +402,7 @@ def process_events(self): mock_neuron.is_null = True with patch('bittensor.Subtensor.difficulty'): - with patch('multiprocessing.Queue.get') as mock_queue_get: + with patch('multiprocessing.queues.Queue.get') as mock_queue_get: mock_queue_get.return_value = None wallet = bittensor.wallet(_mock=True) From 4629b828ca797082f9071eccb433fa935e6fed55 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Fri, 12 Aug 2022 13:55:19 -0400 Subject: [PATCH 40/86] [hotfix] fix flags for multiproc register limit (#876) * add dot get * add to subtensor args and defaults * remove dot get because in subtensor args * typo * fix test --- bittensor/_cli/__init__.py | 17 ----------------- bittensor/_cli/cli_impl.py | 2 +- bittensor/_subtensor/__init__.py | 7 +++++++ tests/integration_tests/test_cli.py | 4 ++-- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 83fe2b8f26..765df518cb 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -571,23 +571,6 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) - register_parser.add_argument( - '--num_processes', - '--num', - '-n', - dest='num_processes', - help="Number of processors to use for registration", - type=int, - default=None, - ) - register_parser.add_argument( - '--update_interval', - '-u', - dest='update_interval', - help="The number of nonces to process before checking for next block during registration", - type=int, - default=None, - ) bittensor.wallet.add_args( register_parser ) bittensor.subtensor.add_args( register_parser ) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 245b943885..54682b1b8f 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -239,7 +239,7 @@ def register( self ): """ wallet = bittensor.wallet( config = self.config ) subtensor = bittensor.subtensor( config = self.config ) - subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, num_processes = self.config.num_processes, update_interval = self.config.update_interval ) + subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, num_processes = self.config.subtensor.register.num_processes, update_interval = self.config.subtensor.register.update_interval ) def transfer( self ): r""" Transfer token of amount to destination. diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 9819c897ed..88a88c7cdf 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -184,6 +184,9 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): help='''The subtensor endpoint flag. If set, overrides the --network flag. ''') parser.add_argument('--' + prefix_str + 'subtensor._mock', action='store_true', help='To turn on subtensor mocking for testing purposes.', default=bittensor.defaults.subtensor._mock) + + parser.add_argument('--' + prefix_str + 'subtensor.register.num_processes', '-n', dest='subtensor.register.num_processes', help="Number of processors to use for registration", type=int, default=bittensor.defaults.subtensor.register.num_processes) + parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) except argparse.ArgumentError: # re-parsing arguments. pass @@ -197,6 +200,10 @@ def add_defaults(cls, defaults ): defaults.subtensor.chain_endpoint = os.getenv('BT_SUBTENSOR_CHAIN_ENDPOINT') if os.getenv('BT_SUBTENSOR_CHAIN_ENDPOINT') != None else None defaults.subtensor._mock = os.getenv('BT_SUBTENSOR_MOCK') if os.getenv('BT_SUBTENSOR_MOCK') != None else False + defaults.subtensor.register = bittensor.Config() + defaults.subtensor.register.num_processes = os.getenv('BT_SUBTENSOR_REGISTER_NUM_PROCESSES') if os.getenv('BT_SUBTENSOR_REGISTER_NUM_PROCESSES') != None else None # uses processor count by default within the function + defaults.subtensor.register.update_interval = os.getenv('BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL') if os.getenv('BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL') != None else 50_000 + @staticmethod def check_config( config: 'bittensor.Config' ): assert config.subtensor diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index 77269d366b..ef76720630 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -1082,8 +1082,8 @@ def test_register( self ): config = self.config config.subtensor._mock = True config.command = "register" - config.num_processes = 1 - config.update_interval = 50_000 + config.subtensor.register.num_processes = 1 + config.subtensor.register.update_interval = 50_000 config.subtensor.network = "mock" config.no_prompt = True From 3d018a7c4eec0185d6591a3466bc0f62c94a61b6 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sun, 14 Aug 2022 01:04:06 -0400 Subject: [PATCH 41/86] add args for cuda to subtensor --- bittensor/_cli/__init__.py | 37 -------------------------------- bittensor/_cli/cli_impl.py | 6 +++--- bittensor/_subtensor/__init__.py | 7 +++++- 3 files changed, 9 insertions(+), 41 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 743e00c960..e4ce73811e 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -252,43 +252,6 @@ def config() -> 'bittensor.config': register_parser = cmd_parsers.add_parser( 'register', help='''Register a wallet to a network.''' - ) - register_parser.add_argument( - '--cuda', - '--cuda.use_cuda', - '--subtensor.cuda.use_cuda', - dest='subtensor.cuda.use_cuda', - default=bittensor.defaults.subtensor.cuda.use_cuda, - help='''Set true to use CUDA.''', - action='store_true', - required=False - ) - register_parser.add_argument( - '--cuda.dev_id', - '--subtensor.cuda.dev_id', - dest='subtensor.cuda.dev_id', - type=int, - default=bittensor.defaults.subtensor.cuda.dev_id, - help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', - required=False - ) - register_parser.add_argument( - '--cuda.TPB', - '--subtensor.cuda.TPB', - dest='subtensor.cuda.TPB', - type=int, - default=bittensor.defaults.subtensor.cuda.TPB, - help='''Set the number of Threads Per Block for CUDA.''', - required=False - ) - register_parser.add_argument( - '--cuda.update_interval', - '--subtensor.cuda.update_interval', - dest='subtensor.cuda.update_interval', - type=int, - default=bittensor.defaults.subtensor.cuda.update_interval, - help='''Set the number of nonces per network update.''', - required=False ) unstake_parser = cmd_parsers.add_parser( diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 504b4ae3c9..26c9eadc08 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -242,11 +242,11 @@ def register( self ): subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, - TPB = self.config.subtensor.cuda.get('TPB', None), + TPB = self.config.subtensor.register.cuda.get('TPB', None), update_interval = self.config.subtensor.register.get('update_interval', None), num_processes = self.config.subtensor.register.get('num_processes', None), - cuda = self.config.subtensor.cuda.get('use_cuda', None), - dev_id = self.config.subtensor.cuda.get('dev_id', None) + cuda = self.config.subtensor.register.cuda.get('use_cuda', None), + dev_id = self.config.subtensor.register.cuda.get('dev_id', None) ) def transfer( self ): diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 5b66400561..10f4e26e10 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -186,7 +186,12 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'subtensor._mock', action='store_true', help='To turn on subtensor mocking for testing purposes.', default=bittensor.defaults.subtensor._mock) parser.add_argument('--' + prefix_str + 'subtensor.register.num_processes', '-n', dest='subtensor.register.num_processes', help="Number of processors to use for registration", type=int, default=bittensor.defaults.subtensor.register.num_processes) - parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) + parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '--' + prefix_str + 'subtensor.register.cuda.update_interval', '--' + prefix_str + 'cuda.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) + + parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest='subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest='subtensor.register.cuda.dev_id', type=int, default=bittensor.defaults.subtensor.cuda.dev_id, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest='subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) + except argparse.ArgumentError: # re-parsing arguments. pass From 5951f7e2638aeec1705ca8364417e8a74cb43439 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sun, 14 Aug 2022 01:07:26 -0400 Subject: [PATCH 42/86] add cuda args to reregister call --- bittensor/_subtensor/__init__.py | 8 ++++---- bittensor/_wallet/wallet_impl.py | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 10f4e26e10..1300a5cab7 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -187,10 +187,10 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'subtensor.register.num_processes', '-n', dest='subtensor.register.num_processes', help="Number of processors to use for registration", type=int, default=bittensor.defaults.subtensor.register.num_processes) parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '--' + prefix_str + 'subtensor.register.cuda.update_interval', '--' + prefix_str + 'cuda.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) - - parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest='subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) - parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest='subtensor.register.cuda.dev_id', type=int, default=bittensor.defaults.subtensor.cuda.dev_id, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) - parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest='subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) + # registration args. Used for register and re-register and anything that calls register. + parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest=f'{prefix_str}subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest=f'{prefix_str}subtensor.register.cuda.dev_id', type=int, default=bittensor.defaults.subtensor.cuda.dev_id, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest=f'{prefix_str}subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) except argparse.ArgumentError: # re-parsing arguments. diff --git a/bittensor/_wallet/wallet_impl.py b/bittensor/_wallet/wallet_impl.py index 158f8d211f..45b28d557e 100644 --- a/bittensor/_wallet/wallet_impl.py +++ b/bittensor/_wallet/wallet_impl.py @@ -245,7 +245,20 @@ def reregister( # Check if the wallet should reregister if not self.config.wallet.get('reregister'): sys.exit(0) - return self.register(subtensor=subtensor, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, prompt=prompt) + + subtensor.register( + wallet = self, + prompt = prompt, + TPB = self.config.subtensor.register.cuda.get('TPB', None), + update_interval = self.config.subtensor.register.cuda.get('update_interval', None), + num_processes = self.config.subtensor.register.get('num_processes', None), + cuda = self.config.subtensor.register.cuda.get('use_cuda', None), + dev_id = self.config.subtensor.register.cuda.get('dev_id', None), + wait_for_inclusion = wait_for_inclusion, + wait_for_finalization = wait_for_finalization, + ) + + return self def register ( self, From 78ba03327e50e01dd020d8a69e072d3737eccfe3 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sun, 14 Aug 2022 01:12:35 -0400 Subject: [PATCH 43/86] add to wallet register the cuda args --- bittensor/_wallet/wallet_impl.py | 62 +++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/bittensor/_wallet/wallet_impl.py b/bittensor/_wallet/wallet_impl.py index 45b28d557e..ddd76d7a7e 100644 --- a/bittensor/_wallet/wallet_impl.py +++ b/bittensor/_wallet/wallet_impl.py @@ -265,26 +265,56 @@ def register ( subtensor: 'bittensor.Subtensor' = None, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, - prompt: bool = False + prompt: bool = False, + max_allowed_attempts: int = 3, + cuda: bool = False, + dev_id: int = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, ) -> 'bittensor.Wallet': - """ Registers this wallet on the chain. - Args: - wait_for_inclusion (bool): - 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. - wait_for_finalization (bool): - 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. - subtensor( 'bittensor.Subtensor' ): - Bittensor subtensor connection. Overrides with defaults if None. - prompt (bool): - If true, the call waits for confirmation from the user before proceeding. - Return: - This wallet. + """ Registers the wallet to chain. + Args: + subtensor( 'bittensor.Subtensor' ): + Bittensor subtensor connection. Overrides with defaults if None. + wait_for_inclusion (bool): + 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. + wait_for_finalization (bool): + 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. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + max_allowed_attempts (int): + Maximum number of attempts to register the wallet. + cuda (bool): + If true, the wallet should be registered on the cuda device. + dev_id (int): + The cuda device id. + TPB (int): + The number of threads per block (cuda). + num_processes (int): + The number of processes to use to register. + update_interval (int): + The number of nonces to solve between updates. + Returns: + success (bool): + flag is true if extrinsic was finalized or uncluded in the block. + If we did not wait for finalization / inclusion, the response is true. """ # Get chain connection. if subtensor == None: subtensor = bittensor.subtensor() - subtensor.register( wallet = self, wait_for_inclusion = wait_for_inclusion, wait_for_finalization = wait_for_finalization, prompt=prompt ) + subtensor.register( + wallet = self, + wait_for_inclusion = wait_for_inclusion, + wait_for_finalization = wait_for_finalization, + prompt=prompt, max_allowed_attempts=max_allowed_attempts, + cuda=cuda, + dev_id=dev_id, + TPB=TPB, + num_processes=num_processes, + update_interval=update_interval + ) return self From 405aeba766da768fb6292c9ec42518c8572af850 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sun, 14 Aug 2022 01:37:56 -0400 Subject: [PATCH 44/86] fix refs and tests --- bittensor/_cli/__init__.py | 10 +++++----- bittensor/_subtensor/__init__.py | 17 ++++++++--------- tests/unit_tests/bittensor_tests/test_neuron.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index e4ce73811e..599b4bd134 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -806,13 +806,13 @@ def check_register_config( config: 'bittensor.Config' ): hotkey = Prompt.ask("Enter hotkey name", default = bittensor.defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) - if not config.no_prompt and config.subtensor.cuda.use_cuda == bittensor.defaults.subtensor.cuda.use_cuda: + if not config.no_prompt and config.subtensor.register.cuda.use_cuda == bittensor.defaults.subtensor.register.cuda.use_cuda: # Ask about cuda registration only if a CUDA device is available. if torch.cuda.is_available(): cuda = Confirm.ask("Would you like to try CUDA registration?\n") - config.subtensor.cuda.use_cuda = cuda + config.subtensor.register.cuda.use_cuda = cuda # Only ask about which CUDA device if the user has more than one CUDA device. - if cuda and config.subtensor.cuda.dev_id == bittensor.defaults.subtensor.cuda.dev_id and torch.cuda.device_count() > 0: + if cuda and config.subtensor.register.cuda.dev_id == bittensor.defaults.subtensor.register.cuda.dev_id and torch.cuda.device_count() > 0: devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] device_names: List[str] = [torch.cuda.get_device_name(x) for x in range(torch.cuda.device_count())] console.print("Available CUDA devices:") @@ -820,13 +820,13 @@ def check_register_config( config: 'bittensor.Config' ): for i, device in enumerate(devices): choices_str += (" {}: {}\n".format(device, device_names[i])) console.print(choices_str) - dev_id = Prompt.ask("Which GPU would you like to use?", choices=devices, default=str(bittensor.defaults.subtensor.cuda.dev_id)) + dev_id = Prompt.ask("Which GPU would you like to use?", choices=devices, default=str(bittensor.defaults.subtensor.register.cuda.dev_id)) try: dev_id = int(dev_id) except ValueError: console.error(":cross_mark:[red]Invalid GPU device[/red] [bold white]{}[/bold white]\nAvailable CUDA devices:{}".format(dev_id, choices_str)) sys.exit(1) - config.subtensor.cuda.dev_id = dev_id + config.subtensor.register.cuda.dev_id = dev_id def check_new_coldkey_config( config: 'bittensor.Config' ): if config.wallet.get('name') == bittensor.defaults.wallet.name and not config.no_prompt: diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 1300a5cab7..0d53fda8b2 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -188,9 +188,9 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'subtensor.register.num_processes', '-n', dest='subtensor.register.num_processes', help="Number of processors to use for registration", type=int, default=bittensor.defaults.subtensor.register.num_processes) parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '--' + prefix_str + 'subtensor.register.cuda.update_interval', '--' + prefix_str + 'cuda.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) # registration args. Used for register and re-register and anything that calls register. - parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest=f'{prefix_str}subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) - parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest=f'{prefix_str}subtensor.register.cuda.dev_id', type=int, default=bittensor.defaults.subtensor.cuda.dev_id, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) - parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest=f'{prefix_str}subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest=f'{prefix_str}subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.register.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest=f'{prefix_str}subtensor.register.cuda.dev_id', type=int, default=bittensor.defaults.subtensor.register.cuda.dev_id, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest=f'{prefix_str}subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.register.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) except argparse.ArgumentError: # re-parsing arguments. @@ -204,17 +204,16 @@ def add_defaults(cls, defaults ): defaults.subtensor.network = os.getenv('BT_SUBTENSOR_NETWORK') if os.getenv('BT_SUBTENSOR_NETWORK') != None else 'nakamoto' defaults.subtensor.chain_endpoint = os.getenv('BT_SUBTENSOR_CHAIN_ENDPOINT') if os.getenv('BT_SUBTENSOR_CHAIN_ENDPOINT') != None else None defaults.subtensor._mock = os.getenv('BT_SUBTENSOR_MOCK') if os.getenv('BT_SUBTENSOR_MOCK') != None else False - defaults.subtensor.cuda = bittensor.Config() - - defaults.subtensor.cuda.dev_id = 0 - defaults.subtensor.cuda.use_cuda = False - defaults.subtensor.cuda.update_interval = 50_000 - defaults.subtensor.cuda.TPB = 256 defaults.subtensor.register = bittensor.Config() defaults.subtensor.register.num_processes = os.getenv('BT_SUBTENSOR_REGISTER_NUM_PROCESSES') if os.getenv('BT_SUBTENSOR_REGISTER_NUM_PROCESSES') != None else None # uses processor count by default within the function defaults.subtensor.register.update_interval = os.getenv('BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL') if os.getenv('BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL') != None else 50_000 + defaults.subtensor.register.cuda = bittensor.Config() + defaults.subtensor.register.cuda.dev_id = 0 + defaults.subtensor.register.cuda.use_cuda = False + defaults.subtensor.register.cuda.TPB = 256 + @staticmethod def check_config( config: 'bittensor.Config' ): assert config.subtensor diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 02ee588d22..4e2ad0ae37 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -67,6 +67,12 @@ def test_coreserver_reregister_flag_false_exit(): config.wallet = bittensor.Config() config.wallet.reregister = False # don't reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config @@ -113,6 +119,12 @@ def test_coreserver_reregister_flag_true(): config.wallet = bittensor.Config() config.wallet.reregister = True # try to reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config From 49a04fb6b3a9dfa9b72540369a46687cc9f011b8 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sun, 14 Aug 2022 01:38:43 -0400 Subject: [PATCH 45/86] add for val test also --- tests/unit_tests/bittensor_tests/test_neuron.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 4e2ad0ae37..676de04f14 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -169,6 +169,12 @@ def test_corevalidator_reregister_flag_false_exit(): config.wallet = bittensor.Config() config.wallet.reregister = False # don't reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config @@ -212,6 +218,12 @@ def test_corevalidator_reregister_flag_true(): config.wallet = bittensor.Config() config.wallet.reregister = True # try to reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config From cefc1dda103ed1107d529d782ffc26227b685c0c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Sun, 14 Aug 2022 01:57:33 -0400 Subject: [PATCH 46/86] fix tests with rereg --- tests/unit_tests/bittensor_tests/test_neuron.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 676de04f14..58c51fef85 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -98,7 +98,7 @@ def exit_early(*args, **kwargs): with patch.multiple( 'bittensor.Wallet', - register=mock_register, + reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): @@ -150,7 +150,7 @@ def exit_early(*args, **kwargs): with patch.multiple( 'bittensor.Wallet', - register=mock_register, + reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): @@ -197,7 +197,7 @@ def exit_early(*args, **kwargs): with patch.multiple( 'bittensor.Wallet', - register=mock_register, + reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): @@ -246,7 +246,7 @@ def exit_early(*args, **kwargs): with patch.multiple( 'bittensor.Wallet', - register=mock_register, + reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): From 224719caa1f105bc6b8d8896365d43c17416095f Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 09:34:01 -0400 Subject: [PATCH 47/86] Fix/diff unpack bit shift (#878) * fix incorrect bit shift * move inner function out and add test for diff pack * fix test * fix call arg check in test * add assert * fix test for py37 * refactor the diff pack into two functions move the test to a unit test * fix test --- bittensor/utils/__init__.py | 35 ++++++++++++------- tests/integration_tests/test_subtensor.py | 13 ++++--- .../bittensor_tests/utils/test_utils.py | 14 ++++++++ 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index a5379e5428..29fe81f878 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -220,7 +220,7 @@ def run(self): with self.check_block: block_number = self.curr_block_num.value block_bytes = bytes(self.curr_block) - block_difficulty = int(self.curr_diff[0] >> 32 | self.curr_diff[1]) + block_difficulty = registration_diff_unpack(self.curr_diff) self.newBlockEvent.clear() # reset nonces to start from random point @@ -270,6 +270,25 @@ def solve_for_nonce_block(solver: Solver, nonce_start: int, nonce_end: int, bloc return None, time.time() - start +def registration_diff_unpack(packed_diff: multiprocessing.Array) -> int: + """Unpacks the packed two 32-bit integers into one 64-bit integer. Little endian.""" + return int(packed_diff[0] << 32 | packed_diff[1]) + + +def registration_diff_pack(diff: int, packed_diff: multiprocessing.Array): + """Packs the difficulty into two 32-bit integers. Little endian.""" + packed_diff[0] = diff >> 32 + packed_diff[1] = diff & 0xFFFFFFFF # low 32 bits + + +def update_curr_block(curr_diff: multiprocessing.Array, curr_block: multiprocessing.Array, curr_block_num: multiprocessing.Value, block_number: int, block_bytes: bytes, diff: int, lock: multiprocessing.Lock): + with lock: + curr_block_num.value = block_number + for i in range(64): + curr_block[i] = block_bytes[i] + registration_diff_pack(diff, curr_diff) + + def solve_for_difficulty_fast( subtensor, wallet, num_processes: Optional[int] = None, update_interval: Optional[int] = None ) -> Optional[POWSolution]: """ Solves the POW for registration using multiprocessing. @@ -306,15 +325,7 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: Optional[int] = curr_block = multiprocessing.Array('h', 64, lock=True) # byte array curr_block_num = multiprocessing.Value('i', 0, lock=True) # int curr_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] - - def update_curr_block(block_number: int, block_bytes: bytes, diff: int, lock: multiprocessing.Lock): - with lock: - curr_block_num.value = block_number - for i in range(64): - curr_block[i] = block_bytes[i] - curr_diff[0] = diff >> 32 - curr_diff[1] = diff & 0xFFFFFFFF # low 32 bits - + status.start() # Establish communication queues @@ -339,7 +350,7 @@ def update_curr_block(block_number: int, block_bytes: bytes, diff: int, lock: mu block_bytes = block_hash.encode('utf-8')[2:] old_block_number = block_number # Set to current block - update_curr_block(block_number, block_bytes, difficulty, check_block) + update_curr_block(curr_diff, curr_block, curr_block_num, block_number, block_bytes, difficulty, check_block) # Set new block events for each solver to start for w in solvers: @@ -373,7 +384,7 @@ def update_curr_block(block_number: int, block_bytes: bytes, diff: int, lock: mu block_bytes = block_hash.encode('utf-8')[2:] difficulty = subtensor.difficulty - update_curr_block(block_number, block_bytes, difficulty, check_block) + update_curr_block(curr_diff, curr_block, curr_block_num, block_number, block_bytes, difficulty, check_block) # Set new block events for each solver for w in solvers: w.newBlockEvent.set() diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index 2d411f1fc0..203c092770 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -15,17 +15,17 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from typing import DefaultDict -from unittest import mock + +import multiprocessing from unittest.mock import patch import bittensor import pytest -import psutil import unittest import time import random from unittest.mock import MagicMock from bittensor.utils.balance import Balance +from bittensor.utils import Solver, update_curr_block from substrateinterface import Keypair from bittensor._subtensor.subtensor_mock import mock_subtensor class TestSubtensor(unittest.TestCase): @@ -411,11 +411,10 @@ def process_events(self): self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) # should return True - assert self.subtensor.register(wallet=wallet,) + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True # calls until True and once again before exiting subtensor class # This assertion is currently broken when difficulty is too low - #assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 - + #assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 def test_registration_partly_failed( self ): class failed(): @@ -448,7 +447,7 @@ def process_events(self): self.subtensor.substrate.submit_extrinsic = MagicMock(side_effect = submit_extrinsic) # should return True - assert self.subtensor.register(wallet=wallet,) == True + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True def test_registration_failed( self ): class failed(): diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 03065ccdca..ba7249c9dd 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -8,6 +8,7 @@ import os import random import torch +import multiprocessing from sys import platform from substrateinterface.base import Keypair @@ -233,5 +234,18 @@ def test_is_valid_ed25519_pubkey(): assert bittensor.utils.is_valid_ed25519_pubkey(good_pubkey) assert not bittensor.utils.is_valid_ed25519_pubkey(bad_pubkey) +def test_registration_diff_pack_unpack(): + fake_diff = pow(2, 31)# this is under 32 bits + + mock_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] + + bittensor.utils.registration_diff_pack(fake_diff, mock_diff) + assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff + + fake_diff = pow(2, 32) * pow(2, 4) # this should be too large if the bit shift is wrong (32 + 4 bits) + + bittensor.utils.registration_diff_pack(fake_diff, mock_diff) + assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff + if __name__ == "__main__": test_solve_for_difficulty_fast_registered_already() \ No newline at end of file From 6be528b788f3048c44f1567db5fd827fee094e31 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 09:42:40 -0400 Subject: [PATCH 48/86] fix patch for tests --- .../unit_tests/bittensor_tests/test_neuron.py | 94 +++++++++---------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 58c51fef85..7433423a74 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -96,23 +96,23 @@ def exit_early(*args, **kwargs): config=config, ) - with patch.multiple( + with patch( 'bittensor.Wallet', - reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): + with patch('bittensor.Subtensor', register=mock_register): - # Should exit without calling register - with pytest.raises(SystemExit) as pytest_wrapped_e: - # Should not raise MockException - bittensor.neurons.core_server.neuron.run( - self=mock_self_neuron - ) - - # Should not try to register the neuron - mock_register.assert_not_called() - assert pytest_wrapped_e.type == SystemExit - assert pytest_wrapped_e.value.code == 0 # No error + # Should exit without calling register + with pytest.raises(SystemExit) as pytest_wrapped_e: + # Should not raise MockException + bittensor.neurons.core_server.neuron.run( + self=mock_self_neuron + ) + + # Should not try to register the neuron + mock_register.assert_not_called() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 # No error def test_coreserver_reregister_flag_true(): config = bittensor.Config() @@ -148,21 +148,21 @@ def exit_early(*args, **kwargs): config=config, ) - with patch.multiple( + with patch( 'bittensor.Wallet', - reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): + with patch('bittensor.Subtensor', register=mock_register): - # Should not exit - with pytest.raises(MockException): - # Should raise MockException - bittensor.neurons.core_server.neuron.run( - self=mock_self_neuron - ) + # Should not exit + with pytest.raises(MockException): + # Should raise MockException + bittensor.neurons.core_server.neuron.run( + self=mock_self_neuron + ) - # Should try to register the neuron - mock_register.assert_called_once() + # Should try to register the neuron + mock_register.assert_called_once() def test_corevalidator_reregister_flag_false_exit(): config = bittensor.Config() @@ -195,23 +195,23 @@ def exit_early(*args, **kwargs): config=config, ) - with patch.multiple( + with patch( 'bittensor.Wallet', - reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): + with patch('bittensor.Subtensor', register=mock_register): - # Should exit without calling register - with pytest.raises(SystemExit) as pytest_wrapped_e: - # Should not raise MockException - bittensor.neurons.core_validator.neuron.__enter__( - self=mock_self_neuron - ) - - # Should not try to register the neuron - mock_register.assert_not_called() - assert pytest_wrapped_e.type == SystemExit - assert pytest_wrapped_e.value.code == 0 # No error + # Should exit without calling register + with pytest.raises(SystemExit) as pytest_wrapped_e: + # Should not raise MockException + bittensor.neurons.core_validator.neuron.__enter__( + self=mock_self_neuron + ) + + # Should not try to register the neuron + mock_register.assert_not_called() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 # No error def test_corevalidator_reregister_flag_true(): config = bittensor.Config() @@ -244,21 +244,21 @@ def exit_early(*args, **kwargs): config=config, ) - with patch.multiple( + with patch( 'bittensor.Wallet', - reregister=mock_register, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): + with patch('bittensor.Subtensor', register=mock_register): - # Should not exit - with pytest.raises(MockException): - # Should raise MockException - bittensor.neurons.core_validator.neuron.__enter__( - self=mock_self_neuron - ) - - # Should try to register the neuron - mock_register.assert_called_once() + # Should not exit + with pytest.raises(MockException): + # Should raise MockException + bittensor.neurons.core_validator.neuron.__enter__( + self=mock_self_neuron + ) + + # Should try to register the neuron + mock_register.assert_called_once() From 1feec114dca8e7e89b494ee516060e020fd261fd Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 09:50:42 -0400 Subject: [PATCH 49/86] add mock_register to subtensor passed instead --- .../unit_tests/bittensor_tests/test_neuron.py | 98 +++++++++---------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 7433423a74..4e62ad8ef0 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -1,3 +1,4 @@ +from atexit import register from types import SimpleNamespace from unittest.mock import MagicMock, patch from more_itertools import side_effect @@ -91,7 +92,8 @@ def exit_early(*args, **kwargs): metagraph=MagicMock(), spec=bittensor.neurons.core_server.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register ), config=config, ) @@ -100,19 +102,18 @@ def exit_early(*args, **kwargs): 'bittensor.Wallet', is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): - with patch('bittensor.Subtensor', register=mock_register): - # Should exit without calling register - with pytest.raises(SystemExit) as pytest_wrapped_e: - # Should not raise MockException - bittensor.neurons.core_server.neuron.run( - self=mock_self_neuron - ) - - # Should not try to register the neuron - mock_register.assert_not_called() - assert pytest_wrapped_e.type == SystemExit - assert pytest_wrapped_e.value.code == 0 # No error + # Should exit without calling register + with pytest.raises(SystemExit) as pytest_wrapped_e: + # Should not raise MockException + bittensor.neurons.core_server.neuron.run( + self=mock_self_neuron + ) + + # Should not try to register the neuron + mock_register.assert_not_called() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 # No error def test_coreserver_reregister_flag_true(): config = bittensor.Config() @@ -143,7 +144,8 @@ def exit_early(*args, **kwargs): metagraph=MagicMock(), spec=bittensor.neurons.core_server.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register, ), config=config, ) @@ -152,17 +154,15 @@ def exit_early(*args, **kwargs): 'bittensor.Wallet', is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): - with patch('bittensor.Subtensor', register=mock_register): - - # Should not exit - with pytest.raises(MockException): - # Should raise MockException - bittensor.neurons.core_server.neuron.run( - self=mock_self_neuron - ) + # Should not exit + with pytest.raises(MockException): + # Should raise MockException + bittensor.neurons.core_server.neuron.run( + self=mock_self_neuron + ) - # Should try to register the neuron - mock_register.assert_called_once() + # Should try to register the neuron + mock_register.assert_called_once() def test_corevalidator_reregister_flag_false_exit(): config = bittensor.Config() @@ -190,7 +190,8 @@ def exit_early(*args, **kwargs): wallet=mock_wallet, spec=bittensor.neurons.core_validator.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register, ), config=config, ) @@ -199,19 +200,18 @@ def exit_early(*args, **kwargs): 'bittensor.Wallet', is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): - with patch('bittensor.Subtensor', register=mock_register): - - # Should exit without calling register - with pytest.raises(SystemExit) as pytest_wrapped_e: - # Should not raise MockException - bittensor.neurons.core_validator.neuron.__enter__( - self=mock_self_neuron - ) - - # Should not try to register the neuron - mock_register.assert_not_called() - assert pytest_wrapped_e.type == SystemExit - assert pytest_wrapped_e.value.code == 0 # No error + + # Should exit without calling register + with pytest.raises(SystemExit) as pytest_wrapped_e: + # Should not raise MockException + bittensor.neurons.core_validator.neuron.__enter__( + self=mock_self_neuron + ) + + # Should not try to register the neuron + mock_register.assert_not_called() + assert pytest_wrapped_e.type == SystemExit + assert pytest_wrapped_e.value.code == 0 # No error def test_corevalidator_reregister_flag_true(): config = bittensor.Config() @@ -239,7 +239,8 @@ def exit_early(*args, **kwargs): wallet=mock_wallet, spec=bittensor.neurons.core_validator.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register, ), config=config, ) @@ -248,17 +249,16 @@ def exit_early(*args, **kwargs): 'bittensor.Wallet', is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): - with patch('bittensor.Subtensor', register=mock_register): - # Should not exit - with pytest.raises(MockException): - # Should raise MockException - bittensor.neurons.core_validator.neuron.__enter__( - self=mock_self_neuron - ) - - # Should try to register the neuron - mock_register.assert_called_once() + # Should not exit + with pytest.raises(MockException): + # Should raise MockException + bittensor.neurons.core_validator.neuron.__enter__( + self=mock_self_neuron + ) + + # Should try to register the neuron + mock_register.assert_called_once() From 94417bd5148c1e3fcba51ef24dcd86399ff32057 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 10:40:19 -0400 Subject: [PATCH 50/86] move register under the check for isregistered --- bittensor/_wallet/wallet_impl.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/bittensor/_wallet/wallet_impl.py b/bittensor/_wallet/wallet_impl.py index ddd76d7a7e..993b09930a 100644 --- a/bittensor/_wallet/wallet_impl.py +++ b/bittensor/_wallet/wallet_impl.py @@ -246,17 +246,17 @@ def reregister( if not self.config.wallet.get('reregister'): sys.exit(0) - subtensor.register( - wallet = self, - prompt = prompt, - TPB = self.config.subtensor.register.cuda.get('TPB', None), - update_interval = self.config.subtensor.register.cuda.get('update_interval', None), - num_processes = self.config.subtensor.register.get('num_processes', None), - cuda = self.config.subtensor.register.cuda.get('use_cuda', None), - dev_id = self.config.subtensor.register.cuda.get('dev_id', None), - wait_for_inclusion = wait_for_inclusion, - wait_for_finalization = wait_for_finalization, - ) + subtensor.register( + wallet = self, + prompt = prompt, + TPB = self.config.subtensor.register.cuda.get('TPB', None), + update_interval = self.config.subtensor.register.cuda.get('update_interval', None), + num_processes = self.config.subtensor.register.get('num_processes', None), + cuda = self.config.subtensor.register.cuda.get('use_cuda', None), + dev_id = self.config.subtensor.register.cuda.get('dev_id', None), + wait_for_inclusion = wait_for_inclusion, + wait_for_finalization = wait_for_finalization, + ) return self From c9114752a00c5e5724fd0a9b12b6dd9a98395a28 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 11:06:39 -0400 Subject: [PATCH 51/86] use patch obj instead --- tests/unit_tests/bittensor_tests/test_neuron.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 4e62ad8ef0..8fe306f886 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -98,8 +98,8 @@ def exit_early(*args, **kwargs): config=config, ) - with patch( - 'bittensor.Wallet', + with patch.object( + mock_wallet, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): @@ -150,8 +150,8 @@ def exit_early(*args, **kwargs): config=config, ) - with patch( - 'bittensor.Wallet', + with patch.object( + mock_wallet, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): # Should not exit @@ -196,8 +196,8 @@ def exit_early(*args, **kwargs): config=config, ) - with patch( - 'bittensor.Wallet', + with patch.object( + mock_wallet, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): @@ -245,8 +245,8 @@ def exit_early(*args, **kwargs): config=config, ) - with patch( - 'bittensor.Wallet', + with patch.object( + mock_wallet, is_registered=MagicMock(return_value=False), # mock the wallet as not registered ): From 16e66ae064ca21b19e75871fa6a939d1f42a9a9a Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 11:15:17 -0400 Subject: [PATCH 52/86] fit patch object --- tests/unit_tests/bittensor_tests/test_neuron.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 8fe306f886..9fa79e7768 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -100,7 +100,7 @@ def exit_early(*args, **kwargs): with patch.object( mock_wallet, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): # Should exit without calling register @@ -152,7 +152,7 @@ def exit_early(*args, **kwargs): with patch.object( mock_wallet, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): # Should not exit with pytest.raises(MockException): @@ -198,7 +198,7 @@ def exit_early(*args, **kwargs): with patch.object( mock_wallet, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): # Should exit without calling register @@ -247,7 +247,7 @@ def exit_early(*args, **kwargs): with patch.object( mock_wallet, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): # Should not exit From c546d3ffe221b9e3edf8e241da58abea990d2520 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 12:28:04 -0400 Subject: [PATCH 53/86] [Feature] [cubit] CUDA registration solver (#868) * added cuda solver * boost versions to fix pip error * allow choosing device id * fix solution check to use keccak * adds params for cuda and dev_id to register * list devices by name during selection * add block number logging * fix calculation of hashrate * fix update interval default * add --TPB arg to register * add update_interval flag * switch back to old looping/work structure * change typing * device count is a function * stop early if wallet registered * add update interval and num proc flag * add better number output * optimize multiproc cpu reg keeping proc until solution * fix test * change import to cubit * fix import and default * up default should have default in CLI call * add comments about params * fix config var access * add cubit as extra * handle stale pow differently check registration after failure * restrict number of processes for integration test * fix stale check * use wallet.is_registered instead * attempt to fix test issue * fix my test * oops typo * typo again ugh * remove print out * fix partly reg test * fix if solution None * fix test? * fix patch * add args for cuda to subtensor * add cuda args to reregister call * add to wallet register the cuda args * fix refs and tests * add for val test also * fix tests with rereg * fix patch for tests * add mock_register to subtensor passed instead * move register under the check for isregistered * use patch obj instead * fit patch object --- bittensor/_cli/__init__.py | 34 ++++- bittensor/_cli/cli_impl.py | 10 +- bittensor/_dataset/dataset_impl.py | 3 +- bittensor/_subtensor/__init__.py | 12 +- bittensor/_subtensor/subtensor_impl.py | 105 +++++++++----- bittensor/_wallet/wallet_impl.py | 77 +++++++--- bittensor/utils/__init__.py | 136 ++++++++++++++++-- bittensor/utils/register_cuda.py | 87 +++++++++++ requirements.txt | 2 +- setup.py | 3 + tests/integration_tests/test_subtensor.py | 40 +++--- .../unit_tests/bittensor_tests/test_neuron.py | 68 ++++++--- 12 files changed, 461 insertions(+), 116 deletions(-) create mode 100644 bittensor/utils/register_cuda.py diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 765df518cb..599b4bd134 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -18,15 +18,18 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. +import argparse import os import sys -import argparse +from typing import List import bittensor -from rich.prompt import Prompt -from rich.prompt import Confirm +import torch +from rich.prompt import Confirm, Prompt from substrateinterface.utils.ss58 import ss58_decode, ss58_encode + from . import cli_impl + console = bittensor.__console__ class cli: @@ -250,6 +253,7 @@ def config() -> 'bittensor.config': 'register', help='''Register a wallet to a network.''' ) + unstake_parser = cmd_parsers.add_parser( 'unstake', help='''Unstake from hotkey accounts.''' @@ -802,6 +806,28 @@ def check_register_config( config: 'bittensor.Config' ): hotkey = Prompt.ask("Enter hotkey name", default = bittensor.defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) + if not config.no_prompt and config.subtensor.register.cuda.use_cuda == bittensor.defaults.subtensor.register.cuda.use_cuda: + # Ask about cuda registration only if a CUDA device is available. + if torch.cuda.is_available(): + cuda = Confirm.ask("Would you like to try CUDA registration?\n") + config.subtensor.register.cuda.use_cuda = cuda + # Only ask about which CUDA device if the user has more than one CUDA device. + if cuda and config.subtensor.register.cuda.dev_id == bittensor.defaults.subtensor.register.cuda.dev_id and torch.cuda.device_count() > 0: + devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] + device_names: List[str] = [torch.cuda.get_device_name(x) for x in range(torch.cuda.device_count())] + console.print("Available CUDA devices:") + choices_str: str = "" + for i, device in enumerate(devices): + choices_str += (" {}: {}\n".format(device, device_names[i])) + console.print(choices_str) + dev_id = Prompt.ask("Which GPU would you like to use?", choices=devices, default=str(bittensor.defaults.subtensor.register.cuda.dev_id)) + try: + dev_id = int(dev_id) + except ValueError: + console.error(":cross_mark:[red]Invalid GPU device[/red] [bold white]{}[/bold white]\nAvailable CUDA devices:{}".format(dev_id, choices_str)) + sys.exit(1) + config.subtensor.register.cuda.dev_id = dev_id + def check_new_coldkey_config( config: 'bittensor.Config' ): if config.wallet.get('name') == bittensor.defaults.wallet.name and not config.no_prompt: wallet_name = Prompt.ask("Enter wallet name", default = bittensor.defaults.wallet.name) @@ -885,4 +911,4 @@ def check_help_config( config: 'bittensor.Config'): def check_update_config( config: 'bittensor.Config'): if not config.no_prompt: answer = Prompt.ask('This will update the local bittensor package', choices = ['Y','N'], default = 'Y') - config.answer = answer \ No newline at end of file + config.answer = answer diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 54682b1b8f..26c9eadc08 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -239,7 +239,15 @@ def register( self ): """ wallet = bittensor.wallet( config = self.config ) subtensor = bittensor.subtensor( config = self.config ) - subtensor.register( wallet = wallet, prompt = not self.config.no_prompt, num_processes = self.config.subtensor.register.num_processes, update_interval = self.config.subtensor.register.update_interval ) + subtensor.register( + wallet = wallet, + prompt = not self.config.no_prompt, + TPB = self.config.subtensor.register.cuda.get('TPB', None), + update_interval = self.config.subtensor.register.get('update_interval', None), + num_processes = self.config.subtensor.register.get('num_processes', None), + cuda = self.config.subtensor.register.cuda.get('use_cuda', None), + dev_id = self.config.subtensor.register.cuda.get('dev_id', None) + ) def transfer( self ): r""" Transfer token of amount to destination. diff --git a/bittensor/_dataset/dataset_impl.py b/bittensor/_dataset/dataset_impl.py index f86f0c7aff..f104b632bf 100644 --- a/bittensor/_dataset/dataset_impl.py +++ b/bittensor/_dataset/dataset_impl.py @@ -160,8 +160,7 @@ def __init__( self.build_hash_table() - if not os.path.isdir(os.path.expanduser(data_dir)): - os.makedirs(os.path.expanduser(data_dir)) + os.makedirs(os.path.expanduser(data_dir), exist_ok=True) self.data_queue = ThreadQueue( producer_target = self.reserve_multiple_data, diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 88a88c7cdf..0d53fda8b2 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -186,7 +186,12 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'subtensor._mock', action='store_true', help='To turn on subtensor mocking for testing purposes.', default=bittensor.defaults.subtensor._mock) parser.add_argument('--' + prefix_str + 'subtensor.register.num_processes', '-n', dest='subtensor.register.num_processes', help="Number of processors to use for registration", type=int, default=bittensor.defaults.subtensor.register.num_processes) - parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) + parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '--' + prefix_str + 'subtensor.register.cuda.update_interval', '--' + prefix_str + 'cuda.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) + # registration args. Used for register and re-register and anything that calls register. + parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest=f'{prefix_str}subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.register.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest=f'{prefix_str}subtensor.register.cuda.dev_id', type=int, default=bittensor.defaults.subtensor.register.cuda.dev_id, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest=f'{prefix_str}subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.register.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) + except argparse.ArgumentError: # re-parsing arguments. pass @@ -204,6 +209,11 @@ def add_defaults(cls, defaults ): defaults.subtensor.register.num_processes = os.getenv('BT_SUBTENSOR_REGISTER_NUM_PROCESSES') if os.getenv('BT_SUBTENSOR_REGISTER_NUM_PROCESSES') != None else None # uses processor count by default within the function defaults.subtensor.register.update_interval = os.getenv('BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL') if os.getenv('BT_SUBTENSOR_REGISTER_UPDATE_INTERVAL') != None else 50_000 + defaults.subtensor.register.cuda = bittensor.Config() + defaults.subtensor.register.cuda.dev_id = 0 + defaults.subtensor.register.cuda.use_cuda = False + defaults.subtensor.register.cuda.TPB = 256 + @staticmethod def check_config( config: 'bittensor.Config' ): assert config.subtensor diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index e71fcca7fa..c58126facd 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -15,8 +15,9 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import torch -from rich.prompt import Confirm +from rich.prompt import Confirm, Prompt from typing import List, Dict, Union, Optional +from multiprocessing import Process import bittensor from tqdm import tqdm @@ -440,6 +441,9 @@ def register ( wait_for_finalization: bool = True, prompt: bool = False, max_allowed_attempts: int = 3, + cuda: bool = False, + dev_id: int = 0, + TPB: int = 256, num_processes: Optional[int] = None, update_interval: Optional[int] = None, ) -> bool: @@ -455,6 +459,18 @@ def register ( or returns false if the extrinsic fails to be finalized within the timeout. prompt (bool): If true, the call waits for confirmation from the user before proceeding. + max_allowed_attempts (int): + Maximum number of attempts to register the wallet. + cuda (bool): + If true, the wallet should be registered on the cuda device. + dev_id (int): + The cuda device id. + TPB (int): + The number of threads per block (cuda). + num_processes (int): + The number of processes to use to register. + update_interval (int): + The number of nonces to solve between updates. Returns: success (bool): flag is true if extrinsic was finalized or uncluded in the block. @@ -475,7 +491,14 @@ def register ( attempts = 1 while True: # Solve latest POW. - pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval ) + if cuda: + if not torch.cuda.is_available(): + if prompt: + bittensor.__console__.error('CUDA is not available.') + return False + pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, num_processes=num_processes, update_interval=update_interval ) + else: + pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval) with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: # pow failed @@ -488,52 +511,62 @@ def register ( # pow successful, proceed to submit pow to chain for registration else: #check if pow result is still valid - while pow_result['block_number'] >= self.get_current_block() - 3: - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module='SubtensorModule', - call_function='register', - call_params={ - 'block_number': pow_result['block_number'], - 'nonce': pow_result['nonce'], - 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), - 'hotkey': wallet.hotkey.ss58_address, - 'coldkey': wallet.coldkeypub.ss58_address - } - ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) - response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + if pow_result['block_number'] < self.get_current_block() - 3: + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + continue + + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='register', + call_params={ + 'block_number': pow_result['block_number'], + 'nonce': pow_result['nonce'], + 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), + 'hotkey': wallet.hotkey.ss58_address, + 'coldkey': wallet.coldkeypub.ss58_address + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + if 'key is already registered' in response.error_message: + # Error meant that the key is already registered. + bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") return True - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) - time.sleep(1) - continue - - # Successful registration, final check for neuron and pubkey - else: - bittensor.__console__.print(":satellite: Checking Balance...") - neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) + + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + time.sleep(1) + + # Successful registration, final check for neuron and pubkey + else: + bittensor.__console__.print(":satellite: Checking Balance...") + if wallet.is_registered( self ): bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") return True + else: + # neuron not found, try again + bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + return False #Failed registration, retry pow attempts += 1 if attempts > max_allowed_attempts: bittensor.__console__.print( "[red]No more attempts.[/red]" ) - return False + return False else: status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) continue - def serve ( self, wallet: 'bittensor.wallet', diff --git a/bittensor/_wallet/wallet_impl.py b/bittensor/_wallet/wallet_impl.py index 158f8d211f..993b09930a 100644 --- a/bittensor/_wallet/wallet_impl.py +++ b/bittensor/_wallet/wallet_impl.py @@ -245,33 +245,76 @@ def reregister( # Check if the wallet should reregister if not self.config.wallet.get('reregister'): sys.exit(0) - return self.register(subtensor=subtensor, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, prompt=prompt) + + subtensor.register( + wallet = self, + prompt = prompt, + TPB = self.config.subtensor.register.cuda.get('TPB', None), + update_interval = self.config.subtensor.register.cuda.get('update_interval', None), + num_processes = self.config.subtensor.register.get('num_processes', None), + cuda = self.config.subtensor.register.cuda.get('use_cuda', None), + dev_id = self.config.subtensor.register.cuda.get('dev_id', None), + wait_for_inclusion = wait_for_inclusion, + wait_for_finalization = wait_for_finalization, + ) + + return self def register ( self, subtensor: 'bittensor.Subtensor' = None, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, - prompt: bool = False + prompt: bool = False, + max_allowed_attempts: int = 3, + cuda: bool = False, + dev_id: int = 0, + TPB: int = 256, + num_processes: Optional[int] = None, + update_interval: Optional[int] = None, ) -> 'bittensor.Wallet': - """ Registers this wallet on the chain. - Args: - wait_for_inclusion (bool): - 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. - wait_for_finalization (bool): - 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. - subtensor( 'bittensor.Subtensor' ): - Bittensor subtensor connection. Overrides with defaults if None. - prompt (bool): - If true, the call waits for confirmation from the user before proceeding. - Return: - This wallet. + """ Registers the wallet to chain. + Args: + subtensor( 'bittensor.Subtensor' ): + Bittensor subtensor connection. Overrides with defaults if None. + wait_for_inclusion (bool): + 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. + wait_for_finalization (bool): + 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. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + max_allowed_attempts (int): + Maximum number of attempts to register the wallet. + cuda (bool): + If true, the wallet should be registered on the cuda device. + dev_id (int): + The cuda device id. + TPB (int): + The number of threads per block (cuda). + num_processes (int): + The number of processes to use to register. + update_interval (int): + The number of nonces to solve between updates. + Returns: + success (bool): + flag is true if extrinsic was finalized or uncluded in the block. + If we did not wait for finalization / inclusion, the response is true. """ # Get chain connection. if subtensor == None: subtensor = bittensor.subtensor() - subtensor.register( wallet = self, wait_for_inclusion = wait_for_inclusion, wait_for_finalization = wait_for_finalization, prompt=prompt ) + subtensor.register( + wallet = self, + wait_for_inclusion = wait_for_inclusion, + wait_for_finalization = wait_for_finalization, + prompt=prompt, max_allowed_attempts=max_allowed_attempts, + cuda=cuda, + dev_id=dev_id, + TPB=TPB, + num_processes=num_processes, + update_interval=update_interval + ) return self diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 29fe81f878..195f9872f6 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,4 +1,5 @@ import binascii +import datetime import hashlib import math import multiprocessing @@ -15,9 +16,11 @@ import requests import torch from Crypto.Hash import keccak -from substrateinterface import Keypair +from substrateinterface import Keypair, KeypairType from substrateinterface.utils import ss58 +from .register_cuda import reset_cuda, solve_cuda + def indexed_values_to_dataframe ( prefix: Union[str, int], @@ -257,7 +260,6 @@ def solve_for_nonce_block(solver: Solver, nonce_start: int, nonce_end: int, bloc # Check if seal meets difficulty product = seal_number * difficulty if product < limit: - print(f"{solver.proc_num} found a solution: {nonce}, {block_number}, {str(block_bytes)}, {str(seal)}, {difficulty}") # Found a solution, save it. return POWSolution(nonce, block_number, difficulty, seal), time.time() - start @@ -416,7 +418,7 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: Optional[int] = except Empty: break - + message = f"""Solving time spent: {time.time() - start_time} Difficulty: [bold white]{millify(difficulty)}[/bold white] @@ -424,23 +426,131 @@ def solve_for_difficulty_fast( subtensor, wallet, num_processes: Optional[int] = Block: [bold white]{block_number}[/bold white] Block_hash: [bold white]{block_hash.encode('utf-8')}[/bold white] Best: [bold white]{binascii.hexlify(bytes(best_seal) if best_seal else bytes(0))}[/bold white]""" - status.update(message.replace(" ", "")) - + status.update(message.replace(" ", "")) + # exited while, solution contains the nonce or wallet is registered stopEvent.set() # stop all other processes status.stop() return solution + +def get_human_readable(num, suffix="H"): + for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]: + if abs(num) < 1000.0: + return f"{num:3.1f}{unit}{suffix}" + num /= 1000.0 + return f"{num:.1f}Y{suffix}" + +def millify(n: int): + millnames = ['',' K',' M',' B',' T'] + n = float(n) + millidx = max(0,min(len(millnames)-1, + int(math.floor(0 if n == 0 else math.log10(abs(n))/3)))) + + return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) + +def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'bittensor.Wallet', update_interval: int = 50_000, TPB: int = 512, dev_id: int = 0 ) -> Optional[POWSolution]: + """ + Solves the registration fast using CUDA + Args: + subtensor: bittensor.Subtensor + The subtensor node to grab blocks + wallet: bittensor.Wallet + The wallet to register + update_interval: int + The number of nonces to try before checking for more blocks + TPB: int + The number of threads per block. CUDA param that should match the GPU capability + dev_id: int + The CUDA device ID to execute the registration on + """ + if not torch.cuda.is_available(): + raise Exception("CUDA not available") + + if update_interval is None: + update_interval = 50_000 + + block_number = subtensor.get_current_block() + difficulty = subtensor.difficulty + block_hash = subtensor.substrate.get_block_hash( block_number ) + while block_hash == None: + block_hash = subtensor.substrate.get_block_hash( block_number ) + block_bytes = block_hash.encode('utf-8')[2:] + + nonce = 0 + limit = int(math.pow(2,256)) - 1 + start_time = time.time() + + console = bittensor.__console__ + status = console.status("Solving") + + solution = -1 + start_time = time.time() + interval_time = start_time + status.start() + while solution == -1 and not wallet.is_registered(subtensor): + solution, seal = solve_cuda(nonce, + update_interval, + TPB, + block_bytes, + block_number, + difficulty, + limit, + dev_id) + + if (solution != -1): + # Attempt to reset CUDA device + reset_cuda() + status.stop() + new_bn = subtensor.get_current_block() + print(f"Found solution for bn: {block_number}; Newest: {new_bn}") + return POWSolution(solution, block_number, difficulty, seal) + + nonce += (TPB * update_interval) + if (nonce >= int(math.pow(2,63))): + nonce = 0 + itrs_per_sec = (TPB * update_interval) / (time.time() - interval_time) + interval_time = time.time() + difficulty = subtensor.difficulty + block_number = subtensor.get_current_block() + block_hash = subtensor.substrate.get_block_hash( block_number) + while block_hash == None: + block_hash = subtensor.substrate.get_block_hash( block_number) + block_bytes = block_hash.encode('utf-8')[2:] + + message = f"""Solving + time spent: {datetime.timedelta(seconds=time.time() - start_time)} + Nonce: [bold white]{nonce}[/bold white] + Difficulty: [bold white]{millify(difficulty)}[/bold white] + Iters: [bold white]{get_human_readable(int(itrs_per_sec), "H")}/s[/bold white] + Block: [bold white]{block_number}[/bold white] + Block_hash: [bold white]{block_hash.encode('utf-8')}[/bold white]""" + status.update(message.replace(" ", "")) + + # exited while, found_solution contains the nonce or wallet is registered + if solution == -1: # didn't find solution + reset_cuda() + status.stop() + return None + + else: + reset_cuda() + # Shouldn't get here + status.stop() + return None + +def create_pow( subtensor, wallet, cuda: bool = False, dev_id: int = 0, tpb: int = 256, num_processes: int = None, update_interval: int = None ) -> Optional[Dict[str, Any]]: + if cuda: + solution: POWSolution = solve_for_difficulty_fast_cuda( subtensor, wallet, dev_id=dev_id, TPB=tpb, update_interval=update_interval ) + else: + solution: POWSolution = solve_for_difficulty_fast( subtensor, wallet, num_processes=num_processes, update_interval=update_interval ) -def create_pow( subtensor, wallet, num_processes: Optional[int] = None, update_interval: Optional[int] = None ) -> Optional[Dict[str, Any]]: - solution: POWSolution = solve_for_difficulty_fast( subtensor, wallet, num_processes=num_processes, update_interval=update_interval ) - nonce, block_number, difficulty, seal = solution.nonce, solution.block_number, solution.difficulty, solution.seal - return None if nonce is None else { - 'nonce': nonce, - 'difficulty': difficulty, - 'block_number': block_number, - 'work': binascii.hexlify(seal) + return None if solution is None else { + 'nonce': solution.nonce, + 'difficulty': solution.difficulty, + 'block_number': solution.block_number, + 'work': binascii.hexlify(solution.seal) } def version_checking(): diff --git a/bittensor/utils/register_cuda.py b/bittensor/utils/register_cuda.py new file mode 100644 index 0000000000..f64f4777b4 --- /dev/null +++ b/bittensor/utils/register_cuda.py @@ -0,0 +1,87 @@ +import binascii +import hashlib +import math +from typing import Tuple + +import numpy as np +from Crypto.Hash import keccak + + +def solve_cuda(nonce_start: np.int64, update_interval: np.int64, TPB: int, block_bytes: bytes, bn: int, difficulty: int, limit: int, dev_id: int = 0) -> Tuple[np.int64, bytes]: + """ + Solves the PoW problem using CUDA. + Args: + nonce_start: int64 + Starting nonce. + update_interval: int64 + Number of nonces to solve before updating block information. + TPB: int + Threads per block. + block_bytes: bytes + Bytes of the block hash. 64 bytes. + difficulty: int256 + Difficulty of the PoW problem. + limit: int256 + Upper limit of the nonce. + dev_id: int (default=0) + The CUDA device ID + Returns: + Tuple[int64, bytes] + Tuple of the nonce and the seal corresponding to the solution. + Returns -1 for nonce if no solution is found. + """ + + try: + import cubit + except ImportError: + raise ImportError("Please install cubit") + + + upper = int(limit // difficulty) + + upper_bytes = upper.to_bytes(32, byteorder='little', signed=False) + + def seal_meets_difficulty( seal:bytes, difficulty:int ): + seal_number = int.from_bytes(seal, "big") + product = seal_number * difficulty + limit = int(math.pow(2,256))- 1 + + return product < limit + + def hex_bytes_to_u8_list( hex_bytes: bytes ): + hex_chunks = [int(hex_bytes[i:i+2], 16) for i in range(0, len(hex_bytes), 2)] + return hex_chunks + + def create_seal_hash( block_bytes:bytes, nonce:int ) -> bytes: + nonce_bytes = binascii.hexlify(nonce.to_bytes(8, 'little')) + pre_seal = nonce_bytes + block_bytes + seal_sh256 = hashlib.sha256( bytearray(hex_bytes_to_u8_list(pre_seal)) ).digest() + kec = keccak.new(digest_bits=256) + seal = kec.update( seal_sh256 ).digest() + return seal + + # Call cython function + # int blockSize, uint64 nonce_start, uint64 update_interval, const unsigned char[:] limit, + # const unsigned char[:] block_bytes, int dev_id + solution = cubit.solve_cuda(TPB, nonce_start, update_interval, upper_bytes, block_bytes, dev_id) # 0 is first GPU + seal = None + if solution != -1: + print(f"Checking solution: {solution} for bn: {bn}") + seal = create_seal_hash(block_bytes, solution) + if seal_meets_difficulty(seal, difficulty): + return solution, seal + else: + return -1, b'\x00' * 32 + + return solution, seal + +def reset_cuda(): + """ + Resets the CUDA environment. + """ + try: + import cubit + except ImportError: + raise ImportError("Please install cubit") + + cubit.reset_cuda() diff --git a/requirements.txt b/requirements.txt index 1e2367e28e..392b7ec0c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,8 +5,8 @@ cryptography>=3.1.1 idna>=2.10 jinja2>=3.0 fuzzywuzzy==0.18.0 +google-api-python-client>=2.6.0 python-levenshtein==0.12.1 -google-api-python-client grpcio==1.42.0 grpcio-tools==1.42.0 hypothesis>=6.47.4 diff --git a/setup.py b/setup.py index 4168052160..d4a9723bcd 100644 --- a/setup.py +++ b/setup.py @@ -69,4 +69,7 @@ 'Topic :: Software Development :: Libraries :: Python Modules', ], python_requires='>=3.7', + extras_requires={ + 'cubit': ['cubit>=1.0.5 @ git+https://github.com/opentensor/cubit.git'] + } ) diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index 203c092770..60639d7a28 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -402,19 +402,21 @@ def process_events(self): mock_neuron.is_null = True with patch('bittensor.Subtensor.difficulty'): + with patch('multiprocessing.queues.Queue.get') as mock_queue_get: + mock_queue_get.return_value = None - wallet = bittensor.wallet(_mock=True) - wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) + wallet = bittensor.wallet(_mock=True) + wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) - self.subtensor.difficulty= 1 - self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) - self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) + self.subtensor.difficulty= 1 + self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) + self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) - # should return True - assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True - # calls until True and once again before exiting subtensor class - # This assertion is currently broken when difficulty is too low - #assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 + # should return True + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True + # calls until True and once again before exiting subtensor class + # This assertion is currently broken when difficulty is too low + assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 def test_registration_partly_failed( self ): class failed(): @@ -430,21 +432,21 @@ def __init__(self): def process_events(self): return True - is_registered_return_values = [False for _ in range(100)] - submit_extrinsic = [failed(), failed(), success()] + submit_extrinsic_mock = MagicMock( side_effect = [failed(), failed(), success()]) + + def is_registered_side_effect(*args, **kwargs): + nonlocal submit_extrinsic_mock + return submit_extrinsic_mock.call_count < 3 + current_block = [i for i in range(0,100)] - mock_neuron = MagicMock() - mock_neuron.is_null = True with patch('bittensor.Subtensor.difficulty'): wallet = bittensor.wallet(_mock=True) - wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) - + wallet.is_registered = MagicMock( side_effect=is_registered_side_effect ) - self.subtensor.difficulty= 1 + self.subtensor.difficulty = 1 self.subtensor.get_current_block = MagicMock(side_effect=current_block) - self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) - self.subtensor.substrate.submit_extrinsic = MagicMock(side_effect = submit_extrinsic) + self.subtensor.substrate.submit_extrinsic = submit_extrinsic_mock # should return True assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True diff --git a/tests/unit_tests/bittensor_tests/test_neuron.py b/tests/unit_tests/bittensor_tests/test_neuron.py index 02ee588d22..9fa79e7768 100644 --- a/tests/unit_tests/bittensor_tests/test_neuron.py +++ b/tests/unit_tests/bittensor_tests/test_neuron.py @@ -1,3 +1,4 @@ +from atexit import register from types import SimpleNamespace from unittest.mock import MagicMock, patch from more_itertools import side_effect @@ -67,6 +68,12 @@ def test_coreserver_reregister_flag_false_exit(): config.wallet = bittensor.Config() config.wallet.reregister = False # don't reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config @@ -85,15 +92,15 @@ def exit_early(*args, **kwargs): metagraph=MagicMock(), spec=bittensor.neurons.core_server.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register ), config=config, ) - with patch.multiple( - 'bittensor.Wallet', - register=mock_register, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + with patch.object( + mock_wallet, + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): # Should exit without calling register @@ -113,6 +120,12 @@ def test_coreserver_reregister_flag_true(): config.wallet = bittensor.Config() config.wallet.reregister = True # try to reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config @@ -131,17 +144,16 @@ def exit_early(*args, **kwargs): metagraph=MagicMock(), spec=bittensor.neurons.core_server.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register, ), config=config, ) - with patch.multiple( - 'bittensor.Wallet', - register=mock_register, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + with patch.object( + mock_wallet, + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): - # Should not exit with pytest.raises(MockException): # Should raise MockException @@ -157,6 +169,12 @@ def test_corevalidator_reregister_flag_false_exit(): config.wallet = bittensor.Config() config.wallet.reregister = False # don't reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config @@ -172,17 +190,17 @@ def exit_early(*args, **kwargs): wallet=mock_wallet, spec=bittensor.neurons.core_validator.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register, ), config=config, ) - with patch.multiple( - 'bittensor.Wallet', - register=mock_register, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + with patch.object( + mock_wallet, + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): - + # Should exit without calling register with pytest.raises(SystemExit) as pytest_wrapped_e: # Should not raise MockException @@ -200,6 +218,12 @@ def test_corevalidator_reregister_flag_true(): config.wallet = bittensor.Config() config.wallet.reregister = True # try to reregister the wallet + config.subtensor = bittensor.Config() + config.subtensor.register = bittensor.Config() + config.subtensor.register.cuda = bittensor.Config() + config.subtensor.register.cuda.use_cuda = False # don't use cuda on test + # No need to specify the other config options as they are default to None + mock_wallet = bittensor.wallet.mock() mock_wallet.config = config @@ -215,15 +239,15 @@ def exit_early(*args, **kwargs): wallet=mock_wallet, spec=bittensor.neurons.core_validator.neuron, subtensor=MagicMock( - network="mock" + network="mock", + register=mock_register, ), config=config, ) - with patch.multiple( - 'bittensor.Wallet', - register=mock_register, - is_registered=MagicMock(return_value=False), # mock the wallet as not registered + with patch.object( + mock_wallet, + 'is_registered', MagicMock(return_value=False), # mock the wallet as not registered ): # Should not exit From fb6be7c306b0c099e549adf95621f4cec3d90483 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 14:46:34 -0400 Subject: [PATCH 54/86] Fix/move overview args to cli (#867) * move cli args to CLI and fix overview * use dot get * fix tests * add hotkeys/all_hotkeys to (un)stake * fix default * fix default in unstake * add unstake multiple * add add stake multiple * move all/hotkeys back to wallet args * convert to balance first add catch for unstake multi * fix ref to wallet * fix test patch for multi hotkeys * try to fix tests * fix tests patch * fix mock wallet length * don't use new? * fix call args get * typo * fix typo --- bittensor/_cli/__init__.py | 25 +- bittensor/_cli/cli_impl.py | 55 ++-- bittensor/_subtensor/subtensor_impl.py | 351 +++++++++++++++++++++++++ bittensor/_wallet/__init__.py | 9 +- tests/integration_tests/test_cli.py | 77 +++--- 5 files changed, 438 insertions(+), 79 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 599b4bd134..97ccd03ead 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -91,6 +91,27 @@ def config() -> 'bittensor.config': help='''Set the output width of the overview. Defaults to automatic width from terminal.''', default=None, ) + overview_parser.add_argument( + '--sort_by', + '--wallet.sort_by', + dest='sort_by', + required=False, + action='store', + default="", + type=str, + help='''Sort the hotkeys by the specified column title (e.g. name, uid, axon).''' + ) + overview_parser.add_argument( + '--sort_order', + '--wallet.sort_order', + dest="sort_order", + required=False, + action='store', + default="ascending", + type=str, + help='''Sort the hotkeys in the specified ordering. (ascending/asc or descending/desc/reverse)''' + ) + bittensor.wallet.add_args( overview_parser ) bittensor.subtensor.add_args( overview_parser ) @@ -501,6 +522,7 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) + bittensor.wallet.add_args( unstake_parser ) bittensor.subtensor.add_args( unstake_parser ) @@ -539,6 +561,7 @@ def config() -> 'bittensor.config': help='''Set true to avoid prompting the user.''', default=False, ) + bittensor.wallet.add_args( stake_parser ) bittensor.subtensor.add_args( stake_parser ) @@ -696,7 +719,7 @@ def check_unstake_config( config: 'bittensor.Config' ): if config.wallet.get('all_hotkeys'): hotkeys = "all hotkeys" elif config.wallet.get('hotkeys'): - hotkeys = str(config.hotkeys).replace('[', '').replace(']', '') + hotkeys = str(config.wallet.hotkeys).replace('[', '').replace(']', '') else: hotkeys = str(config.wallet.hotkey) if not Confirm.ask("Unstake all Tao from: [bold]'{}'[/bold]?".format(hotkeys)): diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index 26c9eadc08..bdce4358dc 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -17,7 +17,7 @@ import os import sys -from typing import List, Union +from typing import List, Union, Optional from cachetools import Cache @@ -259,7 +259,6 @@ def transfer( self ): def unstake( self ): r""" Unstake token of amount from hotkey(s). """ - # TODO: Implement this without re-unlocking the coldkey. config = self.config.copy() config.hotkey = None wallet = bittensor.wallet( config = self.config ) @@ -271,7 +270,7 @@ def unstake( self ): all_hotkeys: List[bittensor.wallet] = self._get_hotkey_wallets_for_wallet( wallet = wallet ) # Exclude hotkeys that are specified. wallets_to_unstake_from = [ - wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys') + wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys', []) ] elif self.config.wallet.get('hotkeys'): @@ -284,9 +283,7 @@ def unstake( self ): subtensor.unstake( wallet, amount = None if self.config.get('unstake_all') else self.config.get('amount'), wait_for_inclusion = True, prompt = not self.config.no_prompt ) return None - wallet_0: 'bittensor.wallet' = wallets_to_unstake_from[0] - # Decrypt coldkey for all wallet(s) to use - wallet_0.coldkey + final_wallets: List['bittensor.wallet'] = [] final_amounts: List[Union[float, Balance]] = [] @@ -295,9 +292,6 @@ def unstake( self ): if not wallet.is_registered(): # Skip unregistered hotkeys. continue - # Assign decrypted coldkey from wallet_0 - # so we don't have to decrypt again - wallet._coldkey = wallet_0._coldkey unstake_amount_tao: float = self.config.get('amount') if self.config.get('max_stake'): @@ -315,19 +309,17 @@ def unstake( self ): if not self.config.no_prompt: if not Confirm.ask("Do you want to unstake from the following keys:\n" + \ "".join([ - f" [bold white]- {wallet.hotkey_str}: {amount}𝜏[/bold white]\n" for wallet, amount in zip(final_wallets, final_amounts) + f" [bold white]- {wallet.hotkey_str}: {amount.tao}𝜏[/bold white]\n" for wallet, amount in zip(final_wallets, final_amounts) ]) ): return None - - for wallet, amount in zip(final_wallets, final_amounts): - subtensor.unstake( wallet, amount = None if self.config.get('unstake_all') else amount, wait_for_inclusion = True, prompt = False ) + + subtensor.unstake_multiple( wallets = final_wallets, amounts = None if self.config.get('unstake_all') else final_amounts, wait_for_inclusion = True, prompt = False ) def stake( self ): r""" Stake token of amount to hotkey(s). """ - # TODO: Implement this without re-unlocking the coldkey. config = self.config.copy() config.hotkey = None wallet = bittensor.wallet( config = config ) @@ -339,7 +331,7 @@ def stake( self ): all_hotkeys: List[bittensor.wallet] = self._get_hotkey_wallets_for_wallet( wallet = wallet ) # Exclude hotkeys that are specified. wallets_to_stake_to = [ - wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys') + wallet for wallet in all_hotkeys if wallet.hotkey_str not in self.config.wallet.get('hotkeys', []) ] elif self.config.wallet.get('hotkeys'): @@ -394,8 +386,7 @@ def stake( self ): ): return None - for wallet, amount in zip(final_wallets, final_amounts): - subtensor.add_stake( wallet, amount = None if self.config.get('stake_all') else amount, wait_for_inclusion = True, prompt = False ) + subtensor.add_stake_multiple( wallets = final_wallets, amounts = None if self.config.get('stake_all') else final_amounts, wait_for_inclusion = True, prompt = False ) def set_weights( self ): @@ -604,24 +595,14 @@ def overview(self): all_hotkeys = [] total_balance = bittensor.Balance(0) - - # We are printing for every wallet. + + # We are printing for every coldkey. if self.config.all: cold_wallets = CLI._get_coldkey_wallets_for_path(self.config.wallet.path) for cold_wallet in tqdm(cold_wallets, desc="Pulling balances"): if cold_wallet.coldkeypub_file.exists_on_device() and not cold_wallet.coldkeypub_file.is_encrypted(): total_balance = total_balance + subtensor.get_balance( cold_wallet.coldkeypub.ss58_address ) all_hotkeys = CLI._get_all_wallets_for_path( self.config.wallet.path ) - - # We are printing for a select number of hotkeys. - elif self.config.wallet.hotkeys: - # Only show hotkeys for wallets in the list - all_hotkeys = [hotkey for hotkey in all_hotkeys if hotkey.hotkey_str in self.config.wallet.hotkeys] - coldkey_wallet = bittensor.wallet( config = self.config ) - if coldkey_wallet.coldkeypub_file.exists_on_device() and not coldkey_wallet.coldkeypub_file.is_encrypted(): - total_balance = subtensor.get_balance( coldkey_wallet.coldkeypub.ss58_address ) - - # We are printing for all keys under the wallet. else: # We are only printing keys for a single coldkey coldkey_wallet = bittensor.wallet( config = self.config ) @@ -632,6 +613,16 @@ def overview(self): return all_hotkeys = CLI._get_hotkey_wallets_for_wallet( coldkey_wallet ) + # We are printing for a select number of hotkeys from all_hotkeys. + + if self.config.wallet.get('hotkeys', []): + if not self.config.get('all_hotkeys', False): + # We are only showing hotkeys that are specified. + all_hotkeys = [hotkey for hotkey in all_hotkeys if hotkey.hotkey_str in self.config.wallet.hotkeys] + else: + # We are excluding the specified hotkeys from all_hotkeys. + all_hotkeys = [hotkey for hotkey in all_hotkeys if hotkey.hotkey_str not in self.config.wallet.hotkeys] + # Check we have keys to display. if len(all_hotkeys) == 0: console.print("[red]No wallets found.[/red]") @@ -740,10 +731,10 @@ def overview(self): console.clear() - sort_by: str = self.config.wallet.sort_by - sort_order: str = self.config.wallet.sort_order + sort_by: Optional[str] = self.config.get('sort_by', None) + sort_order: Optional[str] = self.config.get('sort_order', None) - if sort_by != "": + if sort_by is not None and sort_by != "": column_to_sort_by: int = 0 highest_matching_ratio: int = 0 sort_descending: bool = False # Default sort_order to ascending diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index c58126facd..baac5d3b70 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -762,6 +762,197 @@ def add_stake( return False + def add_stake_multiple ( + self, + wallets: List['bittensor.wallet'], + amounts: List[Union[Balance, float]] = None, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + prompt: bool = False, + ) -> bool: + r""" Adds stake to each wallet hotkey in the list, using each amount, from the common coldkey. + Args: + wallets (List[bittensor.wallet]): + List of wallets to stake. + amounts (List[Union[Balance, float]]): + List of amounts to stake. If None, stake all to the first hotkey. + wait_for_inclusion (bool): + 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. + wait_for_finalization (bool): + 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. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + Returns: + success (bool): + flag is true if extrinsic was finalized or included in the block. + flag is true if any wallet was staked. + If we did not wait for finalization / inclusion, the response is true. + """ + if not isinstance(wallets, list): + raise TypeError("wallets must be a list of bittensor.wallet") + + if len(wallets) == 0: + return True + + if amounts is not None and len(amounts) != len(wallets): + raise ValueError("amounts must be a list of the same length as wallets") + + if amounts is not None and not all(isinstance(amount, (Balance, float)) for amount in amounts): + raise TypeError("amounts must be a [list of bittensor.Balance or float] or None") + + if amounts is None: + amounts = [None] * len(wallets) + else: + # Convert to Balance + amounts = [bittensor.Balance.from_tao(amount) if isinstance(amount, float) else amount for amount in amounts ] + + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return True + + wallet_0: 'bittensor.wallet' = wallets[0] + # Decrypt coldkey for all wallet(s) to use + wallet_0.coldkey + + neurons = [] + with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): + old_balance = self.get_balance( wallet_0.coldkey.ss58_address ) + + for wallet in wallets: + neuron = self.neuron_for_pubkey( ss58_hotkey = wallet.hotkey.ss58_address ) + + if neuron.is_null: + neurons.append( None ) + continue + + neurons.append( neuron ) + + # Remove existential balance to keep key alive. + ## Keys must maintain a balance of at least 1000 rao to stay alive. + total_staking_rao = sum([amount.rao if amount is not None else 0 for amount in amounts]) + if total_staking_rao == 0: + # Staking all to the first wallet. + if old_balance.rao > 1000: + old_balance -= bittensor.Balance.from_rao(1000) + + elif total_staking_rao < 1000: + # Staking less than 1000 rao to the wallets. + pass + else: + # Staking more than 1000 rao to the wallets. + ## Reduce the amount to stake to each wallet to keep the balance above 1000 rao. + percent_reduction = 1 - (1000 / total_staking_rao) + amounts = [amount * percent_reduction for amount in amounts] + + successful_stakes = 0 + for wallet, amount, neuron in zip(wallets, amounts, neurons): + if neuron is None: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + + if wallet.coldkeypub.ss58_address != wallet_0.coldkeypub.ss58_address: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not under the same coldkey. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + + # Assign decrypted coldkey from wallet_0 + # so we don't have to decrypt again + wallet._coldkey = wallet_0._coldkey + staking_all = False + # Convert to bittensor.Balance + if amount == None: + # Stake it all. + staking_balance = bittensor.Balance.from_tao( old_balance.tao ) + staking_all = True + + elif not isinstance(amount, bittensor.Balance ): + staking_balance = bittensor.Balance.from_tao( amount ) + else: + staking_balance = amount + + # Estimate staking fee. + stake_fee = None # To be filled. + with bittensor.__console__.status(":satellite: Estimating Staking Fees..."): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='add_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_staked': staking_balance.rao + } + ) + payment_info = substrate.get_payment_info(call = call, keypair = wallet.coldkey) + if payment_info: + stake_fee = bittensor.Balance.from_rao(payment_info['partialFee']) + bittensor.__console__.print("[green]Estimated Fee: {}[/green]".format( stake_fee )) + else: + stake_fee = bittensor.Balance.from_tao( 0.2 ) + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: could not estimate staking fee, assuming base fee of 0.2") + + # Check enough to stake + if staking_all: + staking_balance -= stake_fee + max(staking_balance, bittensor.Balance.from_tao(0)) + + if staking_balance > old_balance - stake_fee: + bittensor.__console__.print(":cross_mark: [red]Not enough balance[/red]: [green]{}[/green] to stake: [blue]{}[/blue] from coldkey: [white]{}[/white]".format(old_balance, staking_balance, wallet.name)) + continue + + # Ask before moving on. + if prompt: + if not Confirm.ask("Do you want to stake:\n[bold white] amount: {}\n hotkey: {}\n fee: {}[/bold white ]?".format( staking_balance, wallet.hotkey_str, stake_fee) ): + continue + + with bittensor.__console__.status(":satellite: Staking to chain: [white]{}[/white] ...".format(self.network)): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='add_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_staked': staking_balance.rao + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.coldkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion = wait_for_inclusion, wait_for_finalization = wait_for_finalization ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + old_balance -= staking_balance + stake_fee + successful_stakes += 1 + if staking_all: + # If staked all, no need to continue + break + + continue + + response.process_events() + if response.is_success: + bittensor.__console__.print(":white_heavy_check_mark: [green]Finalized[/green]") + else: + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + + if response.is_success: + block = self.get_current_block() + new_stake = bittensor.Balance.from_tao( self.neuron_for_uid( uid = neuron.uid, block = block ).stake) + new_balance = self.get_balance( wallet.coldkey.ss58_address ) + bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( neuron.uid, neuron.stake, new_stake )) + old_balance = new_balance + successful_stakes += 1 + if staking_all: + # If staked all, no need to continue + break + + if successful_stakes != 0: + with bittensor.__console__.status(":satellite: Checking Balance on: ([white]{}[/white] ...".format(self.network)): + new_balance = self.get_balance( wallet.coldkey.ss58_address ) + bittensor.__console__.print("Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance )) + return True + + return False + def transfer( self, wallet: 'bittensor.wallet', @@ -990,6 +1181,166 @@ def unstake ( return True return False + + def unstake_multiple ( + self, + wallets: List['bittensor.wallet'], + amounts: List[Union[Balance, float]] = None, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + prompt: bool = False, + ) -> bool: + r""" Removes stake from each wallet hotkey in the list, using each amount, to their common coldkey. + Args: + wallets (List[bittensor.wallet]): + List of wallets to unstake. + amounts (List[Union[Balance, float]]): + List of amounts to unstake. If None, unstake all. + wait_for_inclusion (bool): + 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. + wait_for_finalization (bool): + 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. + prompt (bool): + If true, the call waits for confirmation from the user before proceeding. + Returns: + success (bool): + flag is true if extrinsic was finalized or included in the block. + flag is true if any wallet was unstaked. + If we did not wait for finalization / inclusion, the response is true. + """ + if not isinstance(wallets, list): + raise TypeError("wallets must be a list of bittensor.wallet") + + if len(wallets) == 0: + return True + + if amounts is not None and len(amounts) != len(wallets): + raise ValueError("amounts must be a list of the same length as wallets") + + if amounts is not None and not all(isinstance(amount, (Balance, float)) for amount in amounts): + raise TypeError("amounts must be a [list of bittensor.Balance or float] or None") + + if amounts is None: + amounts = [None] * len(wallets) + else: + # Convert to Balance + amounts = [bittensor.Balance.from_tao(amount) if isinstance(amount, float) else amount for amount in amounts ] + + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return True + + + wallet_0: 'bittensor.wallet' = wallets[0] + # Decrypt coldkey for all wallet(s) to use + wallet_0.coldkey + + neurons = [] + with bittensor.__console__.status(":satellite: Syncing with chain: [white]{}[/white] ...".format(self.network)): + old_balance = self.get_balance( wallet_0.coldkey.ss58_address ) + + for wallet in wallets: + neuron = self.neuron_for_pubkey( ss58_hotkey = wallet.hotkey.ss58_address ) + + if neuron.is_null: + neurons.append( None ) + continue + + neurons.append( neuron ) + + successful_unstakes = 0 + for wallet, amount, neuron in zip(wallets, amounts, neurons): + if neuron is None: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + + if wallet.coldkeypub.ss58_address != wallet_0.coldkeypub.ss58_address: + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not under the same coldkey. Skipping ...[/red]".format( wallet.hotkey_str )) + continue + + # Assign decrypted coldkey from wallet_0 + # so we don't have to decrypt again + wallet._coldkey = wallet_0._coldkey + + # Covert to bittensor.Balance + if amount == None: + # Unstake it all. + unstaking_balance = bittensor.Balance.from_tao( neuron.stake ) + elif not isinstance(amount, bittensor.Balance ): + unstaking_balance = bittensor.Balance.from_tao( amount ) + else: + unstaking_balance = amount + + # Check enough to unstake. + stake_on_uid = bittensor.Balance.from_tao( neuron.stake ) + if unstaking_balance > stake_on_uid: + bittensor.__console__.print(":cross_mark: [red]Not enough stake[/red]: [green]{}[/green] to unstake: [blue]{}[/blue] from hotkey: [white]{}[/white]".format(stake_on_uid, unstaking_balance, wallet.hotkey_str)) + continue + + # Estimate unstaking fee. + unstake_fee = None # To be filled. + with bittensor.__console__.status(":satellite: Estimating Staking Fees..."): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='remove_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_unstaked': unstaking_balance.rao + } + ) + payment_info = substrate.get_payment_info(call = call, keypair = wallet.coldkey) + if payment_info: + unstake_fee = bittensor.Balance.from_rao(payment_info['partialFee']) + bittensor.__console__.print("[green]Estimated Fee: {}[/green]".format( unstake_fee )) + else: + unstake_fee = bittensor.Balance.from_tao( 0.2 ) + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: could not estimate staking fee, assuming base fee of 0.2") + + # Ask before moving on. + if prompt: + if not Confirm.ask("Do you want to unstake:\n[bold white] amount: {}\n hotkey: {}\n fee: {}[/bold white ]?".format( unstaking_balance, wallet.hotkey_str, unstake_fee) ): + continue + + with bittensor.__console__.status(":satellite: Unstaking from chain: [white]{}[/white] ...".format(self.network)): + with self.substrate as substrate: + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='remove_stake', + call_params={ + 'hotkey': wallet.hotkey.ss58_address, + 'ammount_unstaked': unstaking_balance.rao + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.coldkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion = wait_for_inclusion, wait_for_finalization = wait_for_finalization ) + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + successful_unstakes += 1 + continue + + response.process_events() + if response.is_success: + bittensor.__console__.print(":white_heavy_check_mark: [green]Finalized[/green]") + else: + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + + if response.is_success: + block = self.get_current_block() + new_stake = bittensor.Balance.from_tao( self.neuron_for_uid( uid = neuron.uid, block = block ).stake) + bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( neuron.uid, stake_on_uid, new_stake )) + successful_unstakes += 1 + + if successful_unstakes != 0: + with bittensor.__console__.status(":satellite: Checking Balance on: ([white]{}[/white] ...".format(self.network)): + new_balance = self.get_balance( wallet.coldkey.ss58_address ) + bittensor.__console__.print("Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance )) + return True + + return False def set_weights( self, diff --git a/bittensor/_wallet/__init__.py b/bittensor/_wallet/__init__.py index df8fb4fdf7..9849d50664 100644 --- a/bittensor/_wallet/__init__.py +++ b/bittensor/_wallet/__init__.py @@ -111,11 +111,11 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'wallet.hotkey', required=False, default=bittensor.defaults.wallet.hotkey, help='''The name of wallet's hotkey.''') parser.add_argument('--' + prefix_str + 'wallet.path', required=False, default=bittensor.defaults.wallet.path, help='''The path to your bittensor wallets''') parser.add_argument('--' + prefix_str + 'wallet._mock', action='store_true', default=bittensor.defaults.wallet._mock, help='To turn on wallet mocking for testing purposes.') + parser.add_argument('--' + prefix_str + 'wallet.hotkeys', '--' + prefix_str + 'wallet.exclude_hotkeys', required=False, action='store', default=bittensor.defaults.wallet.hotkeys, type=str, nargs='*', help='''Specify the hotkeys by name. (e.g. hk1 hk2 hk3)''') parser.add_argument('--' + prefix_str + 'wallet.all_hotkeys', required=False, action='store_true', default=bittensor.defaults.wallet.all_hotkeys, help='''To specify all hotkeys. Specifying hotkeys will exclude them from this all.''') - parser.add_argument('--' + prefix_str + 'wallet.sort_by', required=False, action='store', default=bittensor.defaults.wallet.sort_by, type=str, help='''Sort the hotkeys by the specified column title (e.g. name, uid, axon).''') - parser.add_argument('--' + prefix_str + 'wallet.sort_order', required=False, action='store', default=bittensor.defaults.wallet.sort_order, type=str, help='''Sort the hotkeys in the specified ordering. (ascending/asc or descending/desc/reverse)''') parser.add_argument('--' + prefix_str + 'wallet.reregister', required=False, action='store', default=bittensor.defaults.wallet.reregister, type=bool, help='''Whether to reregister the wallet if it is not already registered.''') + except argparse.ArgumentError as e: import pdb #pdb.set_trace() @@ -134,8 +134,6 @@ def add_defaults(cls, defaults): # CLI defaults for Overview defaults.wallet.hotkeys = [] defaults.wallet.all_hotkeys = False - defaults.wallet.sort_by = "" - defaults.wallet.sort_order = "ascending" # Defaults for registration defaults.wallet.reregister = True @@ -148,6 +146,5 @@ def check_config(cls, config: 'bittensor.Config' ): assert isinstance(config.wallet.get('hotkey', bittensor.defaults.wallet.hotkey), str ) or config.wallet.get('hotkey', bittensor.defaults.wallet.hotkey) == None assert isinstance(config.wallet.path, str) assert isinstance(config.wallet.hotkeys, list) - assert isinstance(config.wallet.sort_by, str) - assert isinstance(config.wallet.sort_order, str) assert isinstance(config.wallet.reregister, bool) + assert isinstance(config.wallet.all_hotkeys, bool) diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py index ef76720630..d8276422e0 100644 --- a/tests/integration_tests/test_cli.py +++ b/tests/integration_tests/test_cli.py @@ -442,7 +442,7 @@ def test_unstake_with_specific_hotkeys( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -451,7 +451,7 @@ def test_unstake_with_specific_hotkeys( self ): any_order=True ) mock_unstake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets[1:], amounts=[5.0]*len(mock_wallets[1:]), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -500,11 +500,11 @@ def test_unstake_with_all_hotkeys( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_get_all_wallets.assert_called_once() mock_unstake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets, amounts=[5.0]*len(mock_wallets), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -553,13 +553,11 @@ def test_unstake_with_exclude_hotkeys_from_all( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_get_all_wallets.assert_called_once() mock_unstake.assert_has_calls( - [call(mock_wallets[i], amount=5.0, wait_for_inclusion=True, prompt=False) - for i in (0, 2) # Don't call for hk1 - ], + [call(wallets=[mock_wallets[0], mock_wallets[2]], amounts=[5.0, 5.0], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -628,7 +626,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -637,7 +635,7 @@ def test_unstake_with_multiple_hotkeys_max_stake( self ): any_order=True ) mock_unstake.assert_has_calls( - [call(mock_wallet, amount=CLOSE_IN_VALUE((mock_stakes[mock_wallet.hotkey_str] - config.max_stake).tao, 0.001), wait_for_inclusion=True, prompt=False) for mock_wallet in mock_wallets[1:]], + [call(wallets=mock_wallets[1:], amounts=[CLOSE_IN_VALUE((mock_stakes[mock_wallet.hotkey_str] - config.max_stake).tao, 0.001) for mock_wallet in mock_wallets[1:]], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -706,7 +704,7 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.unstake', return_value=True) as mock_unstake: + with patch('bittensor.Subtensor.unstake_multiple', return_value=True) as mock_unstake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -715,15 +713,16 @@ def test_unstake_with_multiple_hotkeys_max_stake_not_enough_stake( self ): any_order=True ) mock_unstake.assert_called() - for mock_call in mock_unstake.mock_calls: - # Python 3.7 - ## https://docs.python.org/3.7/library/unittest.mock.html#call - ## Uses the 1st index as args list - ## call.args only works in Python 3.8+ - mock_wallet = mock_call[1][0] - # We shouldn't unstake from hk1 as it has less than max_stake staked - assert mock_wallet.hotkey_str != 'hk1' + # Python 3.7 + ## https://docs.python.org/3.7/library/unittest.mock.html#call + ## Uses the 1st index as args list + ## call.args only works in Python 3.8+ + mock_wallets_ = mock_unstake.mock_calls[0][2]['wallets'] + + + # We shouldn't unstake from hk1 as it has less than max_stake staked + assert all(mock_wallet.hotkey_str != 'hk1' for mock_wallet in mock_wallets_) def test_stake_with_specific_hotkeys( self ): bittensor.subtensor.register = MagicMock(return_value = True) @@ -782,7 +781,7 @@ def test_stake_with_specific_hotkeys( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -791,7 +790,7 @@ def test_stake_with_specific_hotkeys( self ): any_order=True ) mock_add_stake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets[1:], amounts=[5.0] * len(mock_wallets[1:]), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -826,7 +825,6 @@ def test_stake_with_all_hotkeys( self ): ) for hk in mock_hotkeys ] - # The 0th wallet is created twice during unstake mock_wallets[0]._coldkey = mock_coldkey mock_wallets[0].coldkey = MagicMock( return_value=mock_coldkey @@ -839,11 +837,11 @@ def test_stake_with_all_hotkeys( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_get_all_wallets.assert_called_once() mock_add_stake.assert_has_calls( - [call(mock_wallets[i+1], amount=5.0, wait_for_inclusion=True, prompt=False) for i in range(len(mock_wallets[1:]))], + [call(wallets=mock_wallets, amounts=[5.0] * len(mock_wallets), wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -891,13 +889,11 @@ def test_stake_with_exclude_hotkeys_from_all( self ): with patch.object(cli, '_get_hotkey_wallets_for_wallet') as mock_get_all_wallets: mock_get_all_wallets.return_value = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_get_all_wallets.assert_called_once() mock_add_stake.assert_has_calls( - [call(mock_wallets[i], amount=5.0, wait_for_inclusion=True, prompt=False) - for i in (0, 2) # Don't call stake for hk1 - ], + [call(wallets=[mock_wallets[0], mock_wallets[2]], amounts=[5.0, 5.0], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -971,7 +967,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -980,7 +976,7 @@ def test_stake_with_multiple_hotkeys_max_stake( self ): any_order=True ) mock_add_stake.assert_has_calls( - [call(mock_wallet, amount=CLOSE_IN_VALUE((config.max_stake - mock_stakes[mock_wallet.hotkey_str].tao), 0.001), wait_for_inclusion=True, prompt=False) for mock_wallet in mock_wallets[1:]], + [call(wallets=mock_wallets[1:], amounts=[CLOSE_IN_VALUE((config.max_stake - mock_stakes[mock_wallet.hotkey_str].tao), 0.001) for mock_wallet in mock_wallets[1:]], wait_for_inclusion=True, prompt=False)], any_order = True ) @@ -1054,7 +1050,7 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): with patch('bittensor.wallet') as mock_create_wallet: mock_create_wallet.side_effect = mock_wallets - with patch('bittensor.Subtensor.add_stake', return_value=True) as mock_add_stake: + with patch('bittensor.Subtensor.add_stake_multiple', return_value=True) as mock_add_stake: cli.run() mock_create_wallet.assert_has_calls( [ @@ -1066,14 +1062,15 @@ def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance( self ): mock_add_stake.assert_called_once() total_staked = 0.0 - for mock_call in mock_add_stake.mock_calls: - # Python 3.7 - ## https://docs.python.org/3.7/library/unittest.mock.html#call - ## Uses the 2nd index as kwargs dict - ## call.kwargs only works in Python 3.8+ - staked_this_call = mock_call[2]['amount'] - - total_staked += staked_this_call + + # Python 3.7 + ## https://docs.python.org/3.7/library/unittest.mock.html#call + ## Uses the 2nd index as kwargs dict + ## call.kwargs only works in Python 3.8+ + amounts_passed = mock_add_stake.mock_calls[0][2]['amounts'] + + total_staked = sum(amounts_passed) + # We should not try to stake more than the mock_balance assert CLOSE_IN_VALUE(total_staked, 0.001) == mock_balance.tao @@ -1265,7 +1262,7 @@ def test_inspect( self ): def test_list( self ): # Mock IO for wallet - with patch('bittensor.wallet.__new__', side_effect=[MagicMock( + with patch('bittensor.wallet', side_effect=[MagicMock( coldkeypub_file=MagicMock( exists_on_device=MagicMock( return_value=True # Wallet exists From e633f44ae90f92a3b2602b9d50903ec8940118b0 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:04:55 -0400 Subject: [PATCH 55/86] fix prompt --- bittensor/_cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 97ccd03ead..838fa07faf 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -832,7 +832,7 @@ def check_register_config( config: 'bittensor.Config' ): if not config.no_prompt and config.subtensor.register.cuda.use_cuda == bittensor.defaults.subtensor.register.cuda.use_cuda: # Ask about cuda registration only if a CUDA device is available. if torch.cuda.is_available(): - cuda = Confirm.ask("Would you like to try CUDA registration?\n") + cuda = Confirm.ask("Detected CUDA device, use CUDA for registration?\n") config.subtensor.register.cuda.use_cuda = cuda # Only ask about which CUDA device if the user has more than one CUDA device. if cuda and config.subtensor.register.cuda.dev_id == bittensor.defaults.subtensor.register.cuda.dev_id and torch.cuda.device_count() > 0: From 850cb14db25bf9a070b691b37cd80e05d8ec65f9 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:14:39 -0400 Subject: [PATCH 56/86] remove unneeded if --- bittensor/_subtensor/subtensor_impl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index baac5d3b70..e5a4351a69 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -865,10 +865,9 @@ def add_stake_multiple ( # Stake it all. staking_balance = bittensor.Balance.from_tao( old_balance.tao ) staking_all = True - - elif not isinstance(amount, bittensor.Balance ): - staking_balance = bittensor.Balance.from_tao( amount ) else: + # Amounts are cast to balance earlier in the function + assert isinstance(amount, bittensor.Balance) staking_balance = amount # Estimate staking fee. From 8ed2b0c6b214d531c6334d045cd374baa056d3de Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:21:45 -0400 Subject: [PATCH 57/86] modify POW submit to use rolling submit again --- bittensor/_subtensor/subtensor_impl.py | 106 ++++++++++++------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index e5a4351a69..ac067adcf2 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -510,62 +510,62 @@ def register ( # pow successful, proceed to submit pow to chain for registration else: - #check if pow result is still valid - if pow_result['block_number'] < self.get_current_block() - 3: - bittensor.__console__.print( "[red]POW is stale.[/red]" ) - continue - - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module='SubtensorModule', - call_function='register', - call_params={ - 'block_number': pow_result['block_number'], - 'nonce': pow_result['nonce'], - 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), - 'hotkey': wallet.hotkey.ss58_address, - 'coldkey': wallet.coldkeypub.ss58_address - } - ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) - response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") - return True - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - if 'key is already registered' in response.error_message: - # Error meant that the key is already registered. - bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") - return True - - bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) - time.sleep(1) + while attempts <= max_allowed_attempts: + #check if pow result is still valid + if pow_result['block_number'] < self.get_current_block() - 3: + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + return False - # Successful registration, final check for neuron and pubkey - else: - bittensor.__console__.print(":satellite: Checking Balance...") - if wallet.is_registered( self ): - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='register', + call_params={ + 'block_number': pow_result['block_number'], + 'nonce': pow_result['nonce'], + 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), + 'hotkey': wallet.hotkey.ss58_address, + 'coldkey': wallet.coldkeypub.ss58_address + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + if 'key is already registered' in response.error_message: + # Error meant that the key is already registered. + bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") + return True + + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey else: - # neuron not found, try again - bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - return False - - #Failed registration, retry pow - attempts += 1 - if attempts > max_allowed_attempts: - bittensor.__console__.print( "[red]No more attempts.[/red]" ) - return False - else: - status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) - continue + bittensor.__console__.print(":satellite: Checking Balance...") + if wallet.is_registered( self ): + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + return True + else: + # neuron not found, try again + bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + continue + + #Failed registration, retry pow + attempts += 1 + status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) + else: + # Failed to register after max attempts. + bittensor.__console__.print( "[red]No more attempts.[/red]" ) + return False def serve ( self, From 65e001d23cada16bef68194a86c0ac83b80ce581 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:34:47 -0400 Subject: [PATCH 58/86] add backoff to block get from network --- bittensor/utils/__init__.py | 30 ++++++++++++++++++------------ requirements.txt | 1 + 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 195f9872f6..ca312d6996 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -11,12 +11,13 @@ from queue import Empty from typing import Any, Dict, Optional, Tuple, Union +import backoff import bittensor import pandas import requests import torch from Crypto.Hash import keccak -from substrateinterface import Keypair, KeypairType +from substrateinterface import Keypair from substrateinterface.utils import ss58 from .register_cuda import reset_cuda, solve_cuda @@ -449,6 +450,18 @@ def millify(n: int): return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) +@backoff.on_exception(backoff.constant, + Exception, + interval=1, + max_tries=3) +def get_block_with_retry(subtensor: 'bittensor.Subtensor') -> Tuple[int, int, bytes]: + block_number = subtensor.get_current_block() + difficulty = subtensor.difficulty + block_hash = subtensor.substrate.get_block_hash( block_number ) + if block_hash is None: + raise Exception("Network error. Could not connect to substrate to get block hash") + return block_number, difficulty, block_hash + def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'bittensor.Wallet', update_interval: int = 50_000, TPB: int = 512, dev_id: int = 0 ) -> Optional[POWSolution]: """ Solves the registration fast using CUDA @@ -469,12 +482,8 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b if update_interval is None: update_interval = 50_000 - - block_number = subtensor.get_current_block() - difficulty = subtensor.difficulty - block_hash = subtensor.substrate.get_block_hash( block_number ) - while block_hash == None: - block_hash = subtensor.substrate.get_block_hash( block_number ) + + block_number, difficulty, block_hash = get_block_with_retry(subtensor) block_bytes = block_hash.encode('utf-8')[2:] nonce = 0 @@ -512,11 +521,8 @@ def solve_for_difficulty_fast_cuda( subtensor: 'bittensor.Subtensor', wallet: 'b nonce = 0 itrs_per_sec = (TPB * update_interval) / (time.time() - interval_time) interval_time = time.time() - difficulty = subtensor.difficulty - block_number = subtensor.get_current_block() - block_hash = subtensor.substrate.get_block_hash( block_number) - while block_hash == None: - block_hash = subtensor.substrate.get_block_hash( block_number) + + block_number, difficulty, block_hash = get_block_with_retry(subtensor) block_bytes = block_hash.encode('utf-8')[2:] message = f"""Solving diff --git a/requirements.txt b/requirements.txt index 392b7ec0c9..1c7f9b8fd6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ argparse base58>=2.0.1 +backoff>=2.1.0 certifi>=2020.11.8 cryptography>=3.1.1 idna>=2.10 From 13b08c76f737d41963d588d1d0d798d700787f89 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:39:33 -0400 Subject: [PATCH 59/86] add test for backoff get block --- .../bittensor_tests/utils/test_utils.py | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index ba7249c9dd..fd13003d3d 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -15,7 +15,7 @@ from _pytest.fixtures import fixture from loguru import logger -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch @@ -235,17 +235,29 @@ def test_is_valid_ed25519_pubkey(): assert not bittensor.utils.is_valid_ed25519_pubkey(bad_pubkey) def test_registration_diff_pack_unpack(): - fake_diff = pow(2, 31)# this is under 32 bits - - mock_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] - - bittensor.utils.registration_diff_pack(fake_diff, mock_diff) - assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff - - fake_diff = pow(2, 32) * pow(2, 4) # this should be too large if the bit shift is wrong (32 + 4 bits) - - bittensor.utils.registration_diff_pack(fake_diff, mock_diff) - assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff + fake_diff = pow(2, 31)# this is under 32 bits + + mock_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] + + bittensor.utils.registration_diff_pack(fake_diff, mock_diff) + assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff + + fake_diff = pow(2, 32) * pow(2, 4) # this should be too large if the bit shift is wrong (32 + 4 bits) + + bittensor.utils.registration_diff_pack(fake_diff, mock_diff) + assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff + +def test_get_block_with_retry_network_error_exit(): + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=1), + difficulty=1, + substrate=MagicMock( + get_block_hash=MagicMock(side_effect=Exception('network error')) + ) + ) + with pytest.raises(Exception): + # this should raise an exception because the network error is retried only 3 times + bittensor.utils.get_block_with_retry(mock_subtensor) if __name__ == "__main__": test_solve_for_difficulty_fast_registered_already() \ No newline at end of file From 98196ffd4a487bb00d146907c0704cde0bc983b6 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:43:13 -0400 Subject: [PATCH 60/86] suppress the dev id flag if not set --- bittensor/_cli/__init__.py | 2 +- bittensor/_subtensor/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/_cli/__init__.py b/bittensor/_cli/__init__.py index 838fa07faf..c9e5ecedfe 100644 --- a/bittensor/_cli/__init__.py +++ b/bittensor/_cli/__init__.py @@ -835,7 +835,7 @@ def check_register_config( config: 'bittensor.Config' ): cuda = Confirm.ask("Detected CUDA device, use CUDA for registration?\n") config.subtensor.register.cuda.use_cuda = cuda # Only ask about which CUDA device if the user has more than one CUDA device. - if cuda and config.subtensor.register.cuda.dev_id == bittensor.defaults.subtensor.register.cuda.dev_id and torch.cuda.device_count() > 0: + if cuda and config.subtensor.register.cuda.get('dev_id') is None and torch.cuda.device_count() > 0: devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] device_names: List[str] = [torch.cuda.get_device_name(x) for x in range(torch.cuda.device_count())] console.print("Available CUDA devices:") diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 0d53fda8b2..5362d1df55 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -189,7 +189,7 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '--' + prefix_str + 'subtensor.register.cuda.update_interval', '--' + prefix_str + 'cuda.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) # registration args. Used for register and re-register and anything that calls register. parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest=f'{prefix_str}subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.register.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) - parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest=f'{prefix_str}subtensor.register.cuda.dev_id', type=int, default=bittensor.defaults.subtensor.register.cuda.dev_id, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) + parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest=f'{prefix_str}subtensor.register.cuda.dev_id', type=int, default=argparse.SUPPRESS, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest=f'{prefix_str}subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.register.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) except argparse.ArgumentError: From c82980958545068132a6dd469ec4b590e30b3892 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:46:00 -0400 Subject: [PATCH 61/86] remove dest so it uses first arg --- bittensor/_subtensor/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index 5362d1df55..a994c1e79a 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -186,11 +186,11 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None ): parser.add_argument('--' + prefix_str + 'subtensor._mock', action='store_true', help='To turn on subtensor mocking for testing purposes.', default=bittensor.defaults.subtensor._mock) parser.add_argument('--' + prefix_str + 'subtensor.register.num_processes', '-n', dest='subtensor.register.num_processes', help="Number of processors to use for registration", type=int, default=bittensor.defaults.subtensor.register.num_processes) - parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '--' + prefix_str + 'subtensor.register.cuda.update_interval', '--' + prefix_str + 'cuda.update_interval', '-u', dest='subtensor.register.update_interval', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) + parser.add_argument('--' + prefix_str + 'subtensor.register.update_interval', '--' + prefix_str + 'subtensor.register.cuda.update_interval', '--' + prefix_str + 'cuda.update_interval', '-u', help="The number of nonces to process before checking for next block during registration", type=int, default=bittensor.defaults.subtensor.register.update_interval) # registration args. Used for register and re-register and anything that calls register. - parser.add_argument( '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', '--' + prefix_str + 'subtensor.register.cuda.use_cuda', dest=f'{prefix_str}subtensor.register.cuda.use_cuda', default=bittensor.defaults.subtensor.register.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) - parser.add_argument( '--' + prefix_str + 'cuda.dev_id', '--' + prefix_str + 'subtensor.register.cuda.dev_id', dest=f'{prefix_str}subtensor.register.cuda.dev_id', type=int, default=argparse.SUPPRESS, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) - parser.add_argument( '--' + prefix_str + 'cuda.TPB', '--' + prefix_str + 'subtensor.register.cuda.TPB', dest=f'{prefix_str}subtensor.register.cuda.TPB', type=int, default=bittensor.defaults.subtensor.register.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) + parser.add_argument( '--' + prefix_str + 'subtensor.register.cuda.use_cuda', '--' + prefix_str + 'cuda', '--' + prefix_str + 'cuda.use_cuda', default=bittensor.defaults.subtensor.register.cuda.use_cuda, help='''Set true to use CUDA.''', action='store_true', required=False ) + parser.add_argument( '--' + prefix_str + 'subtensor.register.cuda.dev_id', '--' + prefix_str + 'cuda.dev_id', type=int, default=argparse.SUPPRESS, help='''Set the CUDA device id. Goes by the order of speed. (i.e. 0 is the fastest).''', required=False ) + parser.add_argument( '--' + prefix_str + 'subtensor.register.cuda.TPB', '--' + prefix_str + 'cuda.TPB', type=int, default=bittensor.defaults.subtensor.register.cuda.TPB, help='''Set the number of Threads Per Block for CUDA.''', required=False ) except argparse.ArgumentError: # re-parsing arguments. From dcaae9fed2d19b10bb6bda5cb1fba6da897e96fd Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 18:59:32 -0400 Subject: [PATCH 62/86] fix pow submit loop --- bittensor/_subtensor/subtensor_impl.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index ac067adcf2..4900eee231 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -510,12 +510,8 @@ def register ( # pow successful, proceed to submit pow to chain for registration else: - while attempts <= max_allowed_attempts: - #check if pow result is still valid - if pow_result['block_number'] < self.get_current_block() - 3: - bittensor.__console__.print( "[red]POW is stale.[/red]" ) - return False - + # check if pow result is still valid + while pow_result['block_number'] >= self.get_current_block() - 3: with self.substrate as substrate: # create extrinsic call call = substrate.compose_call( @@ -558,14 +554,18 @@ def register ( # neuron not found, try again bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") continue - - #Failed registration, retry pow - attempts += 1 - status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) else: - # Failed to register after max attempts. - bittensor.__console__.print( "[red]No more attempts.[/red]" ) - return False + # Exited loop because pow is no longer valid. + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + return False + if attempts <= max_allowed_attempts: + #Failed registration, retry pow + attempts += 1 + status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) + else: + # Failed to register after max attempts. + bittensor.__console__.print( "[red]No more attempts.[/red]" ) + return False def serve ( self, From 3d534b3db03c43d214dc22230ea125e8203292d2 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 19:20:57 -0400 Subject: [PATCH 63/86] move registration status with --- bittensor/_subtensor/subtensor_impl.py | 146 +++++++++++----------- tests/integration_tests/test_subtensor.py | 2 +- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 4900eee231..ed454d19d7 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -489,83 +489,83 @@ def register ( # Attempt rolling registration. attempts = 1 - while True: - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - if prompt: - bittensor.__console__.error('CUDA is not available.') - return False - pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, num_processes=num_processes, update_interval=update_interval ) - else: - pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval) - with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: - - # pow failed - if not pow_result: - # might be registered already - if (wallet.is_registered( self )): - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") - return True - - # pow successful, proceed to submit pow to chain for registration + with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: + while True: + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + if prompt: + bittensor.__console__.error('CUDA is not available.') + return False + pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, num_processes=num_processes, update_interval=update_interval ) else: - # check if pow result is still valid - while pow_result['block_number'] >= self.get_current_block() - 3: - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module='SubtensorModule', - call_function='register', - call_params={ - 'block_number': pow_result['block_number'], - 'nonce': pow_result['nonce'], - 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), - 'hotkey': wallet.hotkey.ss58_address, - 'coldkey': wallet.coldkeypub.ss58_address - } - ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) - response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") - return True - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - if 'key is already registered' in response.error_message: - # Error meant that the key is already registered. - bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") - return True - - bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - else: - bittensor.__console__.print(":satellite: Checking Balance...") - if wallet.is_registered( self ): - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval) + + # pow failed + if not pow_result: + # might be registered already + if (wallet.is_registered( self )): + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + return True + + # pow successful, proceed to submit pow to chain for registration + else: + # check if pow result is still valid + while pow_result['block_number'] >= self.get_current_block() - 3: + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='register', + call_params={ + 'block_number': pow_result['block_number'], + 'nonce': pow_result['nonce'], + 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), + 'hotkey': wallet.hotkey.ss58_address, + 'coldkey': wallet.coldkeypub.ss58_address + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + if 'key is already registered' in response.error_message: + # Error meant that the key is already registered. + bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") + return True + + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey else: - # neuron not found, try again - bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - continue + status.update(":satellite: Checking Balance...") + if wallet.is_registered( self ): + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + return True + else: + # neuron not found, try again + bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + continue + else: + # Exited loop because pow is no longer valid. + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + return False + if attempts <= max_allowed_attempts: + #Failed registration, retry pow + attempts += 1 + status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) else: - # Exited loop because pow is no longer valid. - bittensor.__console__.print( "[red]POW is stale.[/red]" ) - return False - if attempts <= max_allowed_attempts: - #Failed registration, retry pow - attempts += 1 - status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) - else: - # Failed to register after max attempts. - bittensor.__console__.print( "[red]No more attempts.[/red]" ) - return False + # Failed to register after max attempts. + bittensor.__console__.print( "[red]No more attempts.[/red]" ) + return False def serve ( self, diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index 60639d7a28..b76c235c71 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -416,7 +416,7 @@ def process_events(self): assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True # calls until True and once again before exiting subtensor class # This assertion is currently broken when difficulty is too low - assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 + assert wallet.is_registered.call_count == workblocks_before_is_registered + 3 def test_registration_partly_failed( self ): class failed(): From 25814da72415122b63ea30b15d0003c8b2220baa Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 19:45:30 -0400 Subject: [PATCH 64/86] fix max attempts check --- bittensor/_subtensor/subtensor_impl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index ed454d19d7..fd82d155b5 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -500,7 +500,7 @@ def register ( pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, num_processes=num_processes, update_interval=update_interval ) else: pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval) - + # pow failed if not pow_result: # might be registered already @@ -558,7 +558,7 @@ def register ( # Exited loop because pow is no longer valid. bittensor.__console__.print( "[red]POW is stale.[/red]" ) return False - if attempts <= max_allowed_attempts: + if attempts < max_allowed_attempts: #Failed registration, retry pow attempts += 1 status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) From 2f21bc2e0ec74aa072fc89e570bf2c04d747f30b Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 19:49:44 -0400 Subject: [PATCH 65/86] remove status in subtensor.register --- bittensor/_subtensor/subtensor_impl.py | 146 ++++++++++++------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index fd82d155b5..9570d3bd54 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -489,83 +489,83 @@ def register ( # Attempt rolling registration. attempts = 1 - with bittensor.__console__.status(":satellite: Registering...({}/{})".format(attempts,max_allowed_attempts)) as status: - while True: - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - if prompt: - bittensor.__console__.error('CUDA is not available.') - return False - pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, num_processes=num_processes, update_interval=update_interval ) + while True: + bittensor.__console__.print(":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts)) + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + if prompt: + bittensor.__console__.error('CUDA is not available.') + return False + pow_result = bittensor.utils.create_pow( self, wallet, cuda, dev_id, TPB, num_processes=num_processes, update_interval=update_interval ) + else: + pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval) + + # pow failed + if not pow_result: + # might be registered already + if (wallet.is_registered( self )): + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + return True + + # pow successful, proceed to submit pow to chain for registration else: - pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval) - - # pow failed - if not pow_result: - # might be registered already - if (wallet.is_registered( self )): - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") - return True - - # pow successful, proceed to submit pow to chain for registration - else: - # check if pow result is still valid - while pow_result['block_number'] >= self.get_current_block() - 3: - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module='SubtensorModule', - call_function='register', - call_params={ - 'block_number': pow_result['block_number'], - 'nonce': pow_result['nonce'], - 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), - 'hotkey': wallet.hotkey.ss58_address, - 'coldkey': wallet.coldkeypub.ss58_address - } - ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) - response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + # check if pow result is still valid + while pow_result['block_number'] >= self.get_current_block() - 3: + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='register', + call_params={ + 'block_number': pow_result['block_number'], + 'nonce': pow_result['nonce'], + 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), + 'hotkey': wallet.hotkey.ss58_address, + 'coldkey': wallet.coldkeypub.ss58_address + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + if 'key is already registered' in response.error_message: + # Error meant that the key is already registered. + bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") + return True + + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey + else: + bittensor.__console__.print(":satellite: Checking Balance...") + if wallet.is_registered( self ): + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") return True - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - if 'key is already registered' in response.error_message: - # Error meant that the key is already registered. - bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") - return True - - bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey else: - status.update(":satellite: Checking Balance...") - if wallet.is_registered( self ): - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") - return True - else: - # neuron not found, try again - bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - continue - else: - # Exited loop because pow is no longer valid. - bittensor.__console__.print( "[red]POW is stale.[/red]" ) - return False - if attempts < max_allowed_attempts: - #Failed registration, retry pow - attempts += 1 - status.update( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) + # neuron not found, try again + bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + continue else: - # Failed to register after max attempts. - bittensor.__console__.print( "[red]No more attempts.[/red]" ) - return False + # Exited loop because pow is no longer valid. + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + return False + if attempts < max_allowed_attempts: + #Failed registration, retry pow + attempts += 1 + bittensor.__console__.print( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) + else: + # Failed to register after max attempts. + bittensor.__console__.print( "[red]No more attempts.[/red]" ) + return False def serve ( self, From 321eda946e1412f3fa3984f4fd04000be2b492b8 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 19:50:17 -0400 Subject: [PATCH 66/86] add submit status --- bittensor/_subtensor/subtensor_impl.py | 93 +++++++++++++------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 9570d3bd54..29a1003663 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -510,54 +510,55 @@ def register ( # pow successful, proceed to submit pow to chain for registration else: - # check if pow result is still valid - while pow_result['block_number'] >= self.get_current_block() - 3: - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module='SubtensorModule', - call_function='register', - call_params={ - 'block_number': pow_result['block_number'], - 'nonce': pow_result['nonce'], - 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), - 'hotkey': wallet.hotkey.ss58_address, - 'coldkey': wallet.coldkeypub.ss58_address - } - ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) - response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") - return True - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - if 'key is already registered' in response.error_message: - # Error meant that the key is already registered. - bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") - return True - - bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - else: - bittensor.__console__.print(":satellite: Checking Balance...") - if wallet.is_registered( self ): - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + with bittensor.__console__.status(":satellite: Submitting POW..."): + # check if pow result is still valid + while pow_result['block_number'] >= self.get_current_block() - 3: + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='register', + call_params={ + 'block_number': pow_result['block_number'], + 'nonce': pow_result['nonce'], + 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), + 'hotkey': wallet.hotkey.ss58_address, + 'coldkey': wallet.coldkeypub.ss58_address + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + if 'key is already registered' in response.error_message: + # Error meant that the key is already registered. + bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") + return True + + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey else: - # neuron not found, try again - bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - continue - else: - # Exited loop because pow is no longer valid. - bittensor.__console__.print( "[red]POW is stale.[/red]" ) - return False + bittensor.__console__.print(":satellite: Checking Balance...") + if wallet.is_registered( self ): + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + return True + else: + # neuron not found, try again + bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + continue + else: + # Exited loop because pow is no longer valid. + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + return False if attempts < max_allowed_attempts: #Failed registration, retry pow attempts += 1 From 40b464818fcf3c9cad7586ba310e8b15c15e6d90 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 20:04:30 -0400 Subject: [PATCH 67/86] change to neuron get instead --- bittensor/_subtensor/subtensor_impl.py | 3 ++- tests/integration_tests/test_subtensor.py | 2 +- tests/unit_tests/bittensor_tests/utils/test_utils.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 29a1003663..e5f7573a74 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -548,7 +548,8 @@ def register ( # Successful registration, final check for neuron and pubkey else: bittensor.__console__.print(":satellite: Checking Balance...") - if wallet.is_registered( self ): + neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) + if not neuron.is_null: bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") return True else: diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index b76c235c71..3a32d4401d 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -416,7 +416,7 @@ def process_events(self): assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True # calls until True and once again before exiting subtensor class # This assertion is currently broken when difficulty is too low - assert wallet.is_registered.call_count == workblocks_before_is_registered + 3 + assert wallet.is_registered.call_count == workblocks_before_is_registered def test_registration_partly_failed( self ): class failed(): diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index fd13003d3d..4cad49fd0d 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -184,7 +184,7 @@ def test_solve_for_difficulty_fast_registered_already(): assert solution is None # called every time until True - assert wallet.is_registered.call_count == workblocks_before_is_registered + 1 + assert wallet.is_registered.call_count == workblocks_before_is_registered def test_solve_for_difficulty_fast_missing_hash(): block_hash = '0xba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279' From c1da3845f3a4625e6b77fd0118ec0fbf8b2416c0 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 20:10:28 -0400 Subject: [PATCH 68/86] fix count --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 4cad49fd0d..fd13003d3d 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -184,7 +184,7 @@ def test_solve_for_difficulty_fast_registered_already(): assert solution is None # called every time until True - assert wallet.is_registered.call_count == workblocks_before_is_registered + assert wallet.is_registered.call_count == workblocks_before_is_registered + 1 def test_solve_for_difficulty_fast_missing_hash(): block_hash = '0xba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279' From 782e2440ed1914c1ef31061ad1e5b3cd43fa27d1 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 21:33:34 -0400 Subject: [PATCH 69/86] try to patch live display --- tests/integration_tests/test_subtensor.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index 3a32d4401d..e588130a18 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -412,8 +412,14 @@ def process_events(self): self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) - # should return True - assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True + with patch('bittensor.__console__.status') as mock_set_status: + # Need to patch the console status to avoid opening a parallel live display + mock_set_status.__enter__ = MagicMock(return_value=True) + mock_set_status.__exit__ = MagicMock(return_value=True) + + # should return True + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True + # calls until True and once again before exiting subtensor class # This assertion is currently broken when difficulty is too low assert wallet.is_registered.call_count == workblocks_before_is_registered @@ -448,8 +454,13 @@ def is_registered_side_effect(*args, **kwargs): self.subtensor.get_current_block = MagicMock(side_effect=current_block) self.subtensor.substrate.submit_extrinsic = submit_extrinsic_mock - # should return True - assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True + with patch('bittensor.__console__.status') as mock_set_status: + # Need to patch the console status to avoid opening a parallel live display + mock_set_status.__enter__ = MagicMock(return_value=True) + mock_set_status.__exit__ = MagicMock(return_value=True) + + # should return True + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5) == True def test_registration_failed( self ): class failed(): From d0bca31b435b0dc9e6ed8f6377f442828d88dbad Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 21:39:47 -0400 Subject: [PATCH 70/86] fix patch --- tests/integration_tests/test_subtensor.py | 44 ++++++++++++----------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index e588130a18..221ec809ad 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -402,27 +402,29 @@ def process_events(self): mock_neuron.is_null = True with patch('bittensor.Subtensor.difficulty'): - with patch('multiprocessing.queues.Queue.get') as mock_queue_get: - mock_queue_get.return_value = None - - wallet = bittensor.wallet(_mock=True) - wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) - - self.subtensor.difficulty= 1 - self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) - self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) - - with patch('bittensor.__console__.status') as mock_set_status: - # Need to patch the console status to avoid opening a parallel live display - mock_set_status.__enter__ = MagicMock(return_value=True) - mock_set_status.__exit__ = MagicMock(return_value=True) - - # should return True - assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True - - # calls until True and once again before exiting subtensor class - # This assertion is currently broken when difficulty is too low - assert wallet.is_registered.call_count == workblocks_before_is_registered + # patch solution queue to return None + with patch('multiprocessing.queues.Queue.get', return_value=None) as mock_queue_get: + # patch time queue size check + with patch('multiprocessing.queues.Queue.qsize', return_value=0): + + wallet = bittensor.wallet(_mock=True) + wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) + + self.subtensor.difficulty= 1 + self.subtensor.neuron_for_pubkey = MagicMock( return_value=mock_neuron ) + self.subtensor.substrate.submit_extrinsic = MagicMock(return_value = success()) + + with patch('bittensor.__console__.status') as mock_set_status: + # Need to patch the console status to avoid opening a parallel live display + mock_set_status.__enter__ = MagicMock(return_value=True) + mock_set_status.__exit__ = MagicMock(return_value=True) + + # should return True + assert self.subtensor.register(wallet=wallet, num_processes=3, update_interval=5 ) == True + + # calls until True and once again before exiting subtensor class + # This assertion is currently broken when difficulty is too low + assert wallet.is_registered.call_count == workblocks_before_is_registered def test_registration_partly_failed( self ): class failed(): From 75d3a9d14a44b3d3510477f2dae6da9fb18965fb Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Mon, 15 Aug 2022 22:02:42 -0400 Subject: [PATCH 71/86] . --- tests/integration_tests/test_subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_tests/test_subtensor.py b/tests/integration_tests/test_subtensor.py index 221ec809ad..8c1ae1967e 100644 --- a/tests/integration_tests/test_subtensor.py +++ b/tests/integration_tests/test_subtensor.py @@ -406,7 +406,7 @@ def process_events(self): with patch('multiprocessing.queues.Queue.get', return_value=None) as mock_queue_get: # patch time queue size check with patch('multiprocessing.queues.Queue.qsize', return_value=0): - + wallet = bittensor.wallet(_mock=True) wallet.is_registered = MagicMock( side_effect=is_registered_return_values ) @@ -424,7 +424,7 @@ def process_events(self): # calls until True and once again before exiting subtensor class # This assertion is currently broken when difficulty is too low - assert wallet.is_registered.call_count == workblocks_before_is_registered + assert wallet.is_registered.call_count == workblocks_before_is_registered + 2 def test_registration_partly_failed( self ): class failed(): From 364fa8967541dc4cc51c706a74bc802f34710b84 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Aug 2022 08:25:53 -0400 Subject: [PATCH 72/86] separate test cases --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index fd13003d3d..32e6d80b8e 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -234,7 +234,7 @@ def test_is_valid_ed25519_pubkey(): assert bittensor.utils.is_valid_ed25519_pubkey(good_pubkey) assert not bittensor.utils.is_valid_ed25519_pubkey(bad_pubkey) -def test_registration_diff_pack_unpack(): +def test_registration_diff_pack_unpack_under_32_bits(): fake_diff = pow(2, 31)# this is under 32 bits mock_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] @@ -242,6 +242,8 @@ def test_registration_diff_pack_unpack(): bittensor.utils.registration_diff_pack(fake_diff, mock_diff) assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff +def test_registration_diff_pack_unpack_over_32_bits(): + mock_diff = multiprocessing.Array('Q', [0, 0], lock=True) # [high, low] fake_diff = pow(2, 32) * pow(2, 4) # this should be too large if the bit shift is wrong (32 + 4 bits) bittensor.utils.registration_diff_pack(fake_diff, mock_diff) From fdae5baccd143ba9d61e816baabab9861457e444 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Aug 2022 08:33:32 -0400 Subject: [PATCH 73/86] add POWNotStale and tests --- bittensor/_subtensor/subtensor_impl.py | 2 +- bittensor/utils/__init__.py | 6 +++ .../bittensor_tests/utils/test_utils.py | 51 +++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index e5f7573a74..7a83fd99df 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -512,7 +512,7 @@ def register ( else: with bittensor.__console__.status(":satellite: Submitting POW..."): # check if pow result is still valid - while pow_result['block_number'] >= self.get_current_block() - 3: + while bittensor.utils.POWNotStale(pow_result, self): with self.substrate as substrate: # create extrinsic call call = substrate.compose_call( diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index ca312d6996..9d8a201fcb 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -126,6 +126,12 @@ def millify(n: int): return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) +def POWNotStale(pow_result: Dict, subtensor: 'bittensor.Subtensor') -> bool: + """Returns True if the POW is not stale. + This means the block the POW is solved for is within 3 blocks of the current block. + """ + return pow_result['block_number'] >= subtensor.get_current_block() - 3 + @dataclass class POWSolution: """A solution to the registration PoW problem.""" diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 32e6d80b8e..0b6dff3867 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -1,5 +1,6 @@ import binascii import hashlib +import unittest import bittensor import sys import subprocess @@ -261,5 +262,55 @@ def test_get_block_with_retry_network_error_exit(): # this should raise an exception because the network error is retried only 3 times bittensor.utils.get_block_with_retry(mock_subtensor) +class TestPOWNotStale(unittest.TestCase): + def test_pow_not_stale_same_block_number(self): + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=1), + ) + mock_solution = { + "block_number": 1, + } + + assert bittensor.utils.POWNotStale(mock_subtensor, mock_solution) + + def test_pow_not_stale_diff_block_number(self): + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=2), + ) + mock_solution = { + "block_number": 1, # 1 less than current block number + } + + assert bittensor.utils.POWNotStale(mock_subtensor, mock_solution) + + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=3), + ) + mock_solution = { + "block_number": 1, # 2 less than current block number + } + + assert bittensor.utils.POWNotStale(mock_subtensor, mock_solution) + + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=4), + ) + mock_solution = { + "block_number": 1, # 3 less than current block number + } + + assert bittensor.utils.POWNotStale(mock_subtensor, mock_solution) + + def test_pow_not_stale_diff_block_number_too_old(self): + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=5), + ) + mock_solution = { + "block_number": 1, # 4 less than current block number, stale + } + + assert not bittensor.utils.POWNotStale(mock_subtensor, mock_solution) + + if __name__ == "__main__": test_solve_for_difficulty_fast_registered_already() \ No newline at end of file From 7c5a6d08e588eb0f6aa01793cc77ae08d675f794 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Aug 2022 08:39:50 -0400 Subject: [PATCH 74/86] add more test cases for block get with retry --- .../bittensor_tests/utils/test_utils.py | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 0b6dff3867..bc1fba3fae 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -250,18 +250,53 @@ def test_registration_diff_pack_unpack_over_32_bits(): bittensor.utils.registration_diff_pack(fake_diff, mock_diff) assert bittensor.utils.registration_diff_unpack(mock_diff) == fake_diff -def test_get_block_with_retry_network_error_exit(): - mock_subtensor = MagicMock( - get_current_block=MagicMock(return_value=1), - difficulty=1, - substrate=MagicMock( - get_block_hash=MagicMock(side_effect=Exception('network error')) +class TestGetBlockWithRetry(unittest.TestCase): + def test_get_block_with_retry_network_error_exit(self): + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=1), + difficulty=1, + substrate=MagicMock( + get_block_hash=MagicMock(side_effect=Exception('network error')) + ) ) - ) - with pytest.raises(Exception): - # this should raise an exception because the network error is retried only 3 times + with pytest.raises(Exception): + # this should raise an exception because the network error is retried only 3 times + bittensor.utils.get_block_with_retry(mock_subtensor) + + def test_get_block_with_retry_network_error_no_error(self): + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=1), + difficulty=1, + substrate=MagicMock( + get_block_hash=MagicMock(return_value=b'ba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279') + ) + ) + + # this should not raise an exception because there is no error bittensor.utils.get_block_with_retry(mock_subtensor) + def test_get_block_with_retry_network_error_error_twice(self): + # Should retry twice then succeed on the third try + tries = 0 + def throw_error_twice(block_hash: bytes): + nonlocal tries + if tries == 1: + return block_hash + else: + tries += 1 + raise Exception('network error') + + + mock_subtensor = MagicMock( + get_current_block=MagicMock(return_value=1), + difficulty=1, + substrate=MagicMock( + get_block_hash=MagicMock(side_effect=throw_error_twice(b'ba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279')) + ) + ) + + # this should not raise an exception because there is no error on the third try + bittensor.utils.get_block_with_retry(mock_subtensor) class TestPOWNotStale(unittest.TestCase): def test_pow_not_stale_same_block_number(self): mock_subtensor = MagicMock( From e4f90c71cf1e6659149bed4f04677368f1791bcb Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Aug 2022 08:49:41 -0400 Subject: [PATCH 75/86] fix return to None --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index bc1fba3fae..5d0643bc08 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -275,23 +275,23 @@ def test_get_block_with_retry_network_error_no_error(self): # this should not raise an exception because there is no error bittensor.utils.get_block_with_retry(mock_subtensor) - def test_get_block_with_retry_network_error_error_twice(self): + def test_get_block_with_retry_network_error_none_twice(self): # Should retry twice then succeed on the third try tries = 0 - def throw_error_twice(block_hash: bytes): + def block_none_twice(block_hash: bytes): nonlocal tries if tries == 1: return block_hash else: tries += 1 - raise Exception('network error') + return None mock_subtensor = MagicMock( get_current_block=MagicMock(return_value=1), difficulty=1, substrate=MagicMock( - get_block_hash=MagicMock(side_effect=throw_error_twice(b'ba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279')) + get_block_hash=MagicMock(side_effect=block_none_twice(b'ba7ea4eb0b16dee271dbef5911838c3f359fcf598c74da65a54b919b68b67279')) ) ) From 0f9b8bebe907ecd02be88d72319fc7559e81067c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Tue, 16 Aug 2022 08:51:21 -0400 Subject: [PATCH 76/86] fix arg order --- bittensor/_subtensor/subtensor_impl.py | 2 +- bittensor/utils/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 7a83fd99df..48d67d07e7 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -512,7 +512,7 @@ def register ( else: with bittensor.__console__.status(":satellite: Submitting POW..."): # check if pow result is still valid - while bittensor.utils.POWNotStale(pow_result, self): + while bittensor.utils.POWNotStale(self, pow_result): with self.substrate as substrate: # create extrinsic call call = substrate.compose_call( diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 9d8a201fcb..3a5b353b8d 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -126,7 +126,7 @@ def millify(n: int): return '{:.0f}{}'.format(n / 10**(3 * millidx), millnames[millidx]) -def POWNotStale(pow_result: Dict, subtensor: 'bittensor.Subtensor') -> bool: +def POWNotStale(subtensor: 'bittensor.Subtensor', pow_result: Dict) -> bool: """Returns True if the POW is not stale. This means the block the POW is solved for is within 3 blocks of the current block. """ From 986e7a06f56e9908abb8cdc194b8334d81b0ecb9 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 11:36:01 -0400 Subject: [PATCH 77/86] fix indent --- bittensor/_subtensor/subtensor_impl.py | 114 ++++++++++++------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 48d67d07e7..b5596fea07 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -501,65 +501,65 @@ def register ( else: pow_result = bittensor.utils.create_pow( self, wallet, num_processes=num_processes, update_interval=update_interval) - # pow failed - if not pow_result: - # might be registered already - if (wallet.is_registered( self )): - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") - return True - - # pow successful, proceed to submit pow to chain for registration - else: - with bittensor.__console__.status(":satellite: Submitting POW..."): - # check if pow result is still valid - while bittensor.utils.POWNotStale(self, pow_result): - with self.substrate as substrate: - # create extrinsic call - call = substrate.compose_call( - call_module='SubtensorModule', - call_function='register', - call_params={ - 'block_number': pow_result['block_number'], - 'nonce': pow_result['nonce'], - 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), - 'hotkey': wallet.hotkey.ss58_address, - 'coldkey': wallet.coldkeypub.ss58_address - } - ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) - response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + # pow failed + if not pow_result: + # might be registered already + if (wallet.is_registered( self )): + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") + return True + + # pow successful, proceed to submit pow to chain for registration + else: + with bittensor.__console__.status(":satellite: Submitting POW..."): + # check if pow result is still valid + while bittensor.utils.POWNotStale(self, pow_result): + with self.substrate as substrate: + # create extrinsic call + call = substrate.compose_call( + call_module='SubtensorModule', + call_function='register', + call_params={ + 'block_number': pow_result['block_number'], + 'nonce': pow_result['nonce'], + 'work': bittensor.utils.hex_bytes_to_u8_list( pow_result['work'] ), + 'hotkey': wallet.hotkey.ss58_address, + 'coldkey': wallet.coldkeypub.ss58_address + } + ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization ) + + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + bittensor.__console__.print(":white_heavy_check_mark: [green]Sent[/green]") + return True + + # process if registration successful, try again if pow is still valid + response.process_events() + if not response.is_success: + if 'key is already registered' in response.error_message: + # Error meant that the key is already registered. + bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") + return True + + bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) + time.sleep(0.5) + + # Successful registration, final check for neuron and pubkey + else: + bittensor.__console__.print(":satellite: Checking Balance...") + neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) + if not neuron.is_null: + bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") return True - - # process if registration successful, try again if pow is still valid - response.process_events() - if not response.is_success: - if 'key is already registered' in response.error_message: - # Error meant that the key is already registered. - bittensor.__console__.print(":white_heavy_check_mark: [green]Already Registered[/green]") - return True - - bittensor.__console__.print(":cross_mark: [red]Failed[/red]: error:{}".format(response.error_message)) - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey else: - bittensor.__console__.print(":satellite: Checking Balance...") - neuron = self.neuron_for_pubkey( wallet.hotkey.ss58_address ) - if not neuron.is_null: - bittensor.__console__.print(":white_heavy_check_mark: [green]Registered[/green]") - return True - else: - # neuron not found, try again - bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - continue - else: - # Exited loop because pow is no longer valid. - bittensor.__console__.print( "[red]POW is stale.[/red]" ) - return False + # neuron not found, try again + bittensor.__console__.print(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + continue + else: + # Exited loop because pow is no longer valid. + bittensor.__console__.print( "[red]POW is stale.[/red]" ) + return False if attempts < max_allowed_attempts: #Failed registration, retry pow attempts += 1 From edf635cc9d3de5228451776e539bb5d19d510315 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 11:42:03 -0400 Subject: [PATCH 78/86] add test to verify solution is submitted --- .../bittensor_tests/utils/test_utils.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 5d0643bc08..108ae6d3eb 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -10,6 +10,7 @@ import random import torch import multiprocessing +from types import SimpleNamespace from sys import platform from substrateinterface.base import Keypair @@ -346,6 +347,56 @@ def test_pow_not_stale_diff_block_number_too_old(self): assert not bittensor.utils.POWNotStale(mock_subtensor, mock_solution) +def test_pow_called_for_cuda(): + class MockException(Exception): + pass + mock_compose_call = MagicMock(side_effect=MockException) + + mock_subtensor = bittensor.subtensor(_mock=True) + mock_subtensor.neuron_for_pubkey=MagicMock(is_null=True) + mock_subtensor.substrate = MagicMock( + __enter__= MagicMock(return_value=MagicMock( + compose_call=mock_compose_call + )), + __exit__ = MagicMock(return_value=None), + ) + + mock_wallet = SimpleNamespace( + hotkey=SimpleNamespace( + ss58_address='' + ), + colkeypub=SimpleNamespace( + ss58_address='' + ) + ) + + mock_result = { + "block_number": 1, + 'nonce': random.randint(0, pow(2, 32)), + 'work': b'\x00' * 32, + } + + with patch('bittensor.utils.POWNotStale', return_value=True) as mock_pow_not_stale: + with patch('torch.cuda.is_available', return_value=True) as mock_cuda_available: + with patch('bittensor.utils.create_pow', return_value=mock_result) as mock_create_pow: + + # Should exit early + with pytest.raises(MockException): + mock_subtensor.register(mock_subtensor, mock_wallet, cuda=True, prompt=False) + + mock_pow_not_stale.assert_called_once() + mock_create_pow.assert_called_once() + mock_cuda_available.assert_called_once() + + call0 = mock_pow_not_stale.call_args + assert call0[0][0] == mock_subtensor + assert call0[0][1] == mock_result + + mock_compose_call.assert_called_once() + call1 = mock_compose_call.call_args + assert call1[0][2]['call_function'] == 'register' + call_params = call1[0][2]['call_params'] + assert call_params['nonce'] == mock_result.nonce if __name__ == "__main__": test_solve_for_difficulty_fast_registered_already() \ No newline at end of file From 5b4a13bb0e667589d718b34996f79fdeb5c90573 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 11:49:10 -0400 Subject: [PATCH 79/86] fix mock call --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 108ae6d3eb..6314d00b48 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -382,7 +382,7 @@ class MockException(Exception): # Should exit early with pytest.raises(MockException): - mock_subtensor.register(mock_subtensor, mock_wallet, cuda=True, prompt=False) + mock_subtensor.register(mock_wallet, cuda=True, prompt=False) mock_pow_not_stale.assert_called_once() mock_create_pow.assert_called_once() From 497edeb130e06894083df594d79a46c6fe38a68c Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 12:04:48 -0400 Subject: [PATCH 80/86] patch hex bytes instead --- .../bittensor_tests/utils/test_utils.py | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 6314d00b48..6376f122b6 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -373,30 +373,31 @@ class MockException(Exception): mock_result = { "block_number": 1, 'nonce': random.randint(0, pow(2, 32)), - 'work': b'\x00' * 32, + 'work': b'\x00' * 64, } with patch('bittensor.utils.POWNotStale', return_value=True) as mock_pow_not_stale: with patch('torch.cuda.is_available', return_value=True) as mock_cuda_available: with patch('bittensor.utils.create_pow', return_value=mock_result) as mock_create_pow: + with patch('bittensor.utils.hex_bytes_to_u8_list', return_value=b''): - # Should exit early - with pytest.raises(MockException): - mock_subtensor.register(mock_wallet, cuda=True, prompt=False) - - mock_pow_not_stale.assert_called_once() - mock_create_pow.assert_called_once() - mock_cuda_available.assert_called_once() - - call0 = mock_pow_not_stale.call_args - assert call0[0][0] == mock_subtensor - assert call0[0][1] == mock_result - - mock_compose_call.assert_called_once() - call1 = mock_compose_call.call_args - assert call1[0][2]['call_function'] == 'register' - call_params = call1[0][2]['call_params'] - assert call_params['nonce'] == mock_result.nonce + # Should exit early + with pytest.raises(MockException): + mock_subtensor.register(mock_wallet, cuda=True, prompt=False) + + mock_pow_not_stale.assert_called_once() + mock_create_pow.assert_called_once() + mock_cuda_available.assert_called_once() + + call0 = mock_pow_not_stale.call_args + assert call0[0][0] == mock_subtensor + assert call0[0][1] == mock_result + + mock_compose_call.assert_called_once() + call1 = mock_compose_call.call_args + assert call1[0][2]['call_function'] == 'register' + call_params = call1[0][2]['call_params'] + assert call_params['nonce'] == mock_result.nonce if __name__ == "__main__": test_solve_for_difficulty_fast_registered_already() \ No newline at end of file From 85c8ae4e401c066cfdb9f16c4816fc9f191f17e4 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 12:08:01 -0400 Subject: [PATCH 81/86] typo :/ --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 6376f122b6..12d2259451 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -365,7 +365,7 @@ class MockException(Exception): hotkey=SimpleNamespace( ss58_address='' ), - colkeypub=SimpleNamespace( + coldkeypub=SimpleNamespace( ss58_address='' ) ) From 0b066f908beddaefb6ef5c73b527c3ad893083fa Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 12:09:58 -0400 Subject: [PATCH 82/86] fix print out for unstake --- bittensor/_cli/cli_impl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/_cli/cli_impl.py b/bittensor/_cli/cli_impl.py index bdce4358dc..bdc4744e1a 100644 --- a/bittensor/_cli/cli_impl.py +++ b/bittensor/_cli/cli_impl.py @@ -309,7 +309,7 @@ def unstake( self ): if not self.config.no_prompt: if not Confirm.ask("Do you want to unstake from the following keys:\n" + \ "".join([ - f" [bold white]- {wallet.hotkey_str}: {amount.tao}𝜏[/bold white]\n" for wallet, amount in zip(final_wallets, final_amounts) + f" [bold white]- {wallet.hotkey_str}: {amount}𝜏[/bold white]\n" for wallet, amount in zip(final_wallets, final_amounts) ]) ): return None From dd778207f39932fe5c92e3b4a26f578e403dd801 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 12:15:04 -0400 Subject: [PATCH 83/86] fix indexing into mock call --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 12d2259451..3ab506b286 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -395,8 +395,8 @@ class MockException(Exception): mock_compose_call.assert_called_once() call1 = mock_compose_call.call_args - assert call1[0][2]['call_function'] == 'register' - call_params = call1[0][2]['call_params'] + assert call1[2]['call_function'] == 'register' + call_params = call1[2]['call_params'] assert call_params['nonce'] == mock_result.nonce if __name__ == "__main__": From 751d9a2be864ea2b5102874252eecd14012649fe Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 12:22:04 -0400 Subject: [PATCH 84/86] call indexing --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index 3ab506b286..ed4fac4c82 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -395,8 +395,8 @@ class MockException(Exception): mock_compose_call.assert_called_once() call1 = mock_compose_call.call_args - assert call1[2]['call_function'] == 'register' - call_params = call1[2]['call_params'] + assert call1[1]['call_function'] == 'register' + call_params = call1[1]['call_params'] assert call_params['nonce'] == mock_result.nonce if __name__ == "__main__": From a2339d72b4695523cb5d42d36c7ecac78a02acdf Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 12:29:25 -0400 Subject: [PATCH 85/86] access dict not with dot --- tests/unit_tests/bittensor_tests/utils/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/bittensor_tests/utils/test_utils.py b/tests/unit_tests/bittensor_tests/utils/test_utils.py index ed4fac4c82..d3bad0f6f6 100644 --- a/tests/unit_tests/bittensor_tests/utils/test_utils.py +++ b/tests/unit_tests/bittensor_tests/utils/test_utils.py @@ -397,7 +397,7 @@ class MockException(Exception): call1 = mock_compose_call.call_args assert call1[1]['call_function'] == 'register' call_params = call1[1]['call_params'] - assert call_params['nonce'] == mock_result.nonce + assert call_params['nonce'] == mock_result['nonce'] if __name__ == "__main__": test_solve_for_difficulty_fast_registered_already() \ No newline at end of file From a9d4f519fe3f1913f6ecdb33d8d026b2387c0092 Mon Sep 17 00:00:00 2001 From: Cameron Fairchild Date: Wed, 17 Aug 2022 12:40:26 -0400 Subject: [PATCH 86/86] fix other indent --- bittensor/_subtensor/subtensor_impl.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index b5596fea07..c05f3a68da 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -560,14 +560,15 @@ def register ( # Exited loop because pow is no longer valid. bittensor.__console__.print( "[red]POW is stale.[/red]" ) return False - if attempts < max_allowed_attempts: - #Failed registration, retry pow - attempts += 1 - bittensor.__console__.print( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) - else: - # Failed to register after max attempts. - bittensor.__console__.print( "[red]No more attempts.[/red]" ) - return False + + if attempts < max_allowed_attempts: + #Failed registration, retry pow + attempts += 1 + bittensor.__console__.print( ":satellite: Failed registration, retrying pow ...({}/{})".format(attempts, max_allowed_attempts)) + else: + # Failed to register after max attempts. + bittensor.__console__.print( "[red]No more attempts.[/red]" ) + return False def serve ( self,