Source code for bittensor.core.extrinsics.registration

# The MIT License (MIT)
# Copyright © 2024 Opentensor Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# 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 time
from typing import Union, Optional, TYPE_CHECKING

from bittensor_wallet.errors import KeyFileError
from retry import retry

from bittensor.utils import format_error_message
from bittensor.utils.btlogging import logging
from bittensor.utils.networking import ensure_connected
from bittensor.utils.registration import (
    POWSolution,
    create_pow,
    torch,
    log_no_torch_error,
)

# For annotation purposes
if TYPE_CHECKING:
    from bittensor.core.subtensor import Subtensor
    from bittensor_wallet import Wallet


[docs] @ensure_connected def _do_pow_register( self: "Subtensor", netuid: int, wallet: "Wallet", pow_result: "POWSolution", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> tuple[bool, Optional[str]]: """Sends a (POW) register extrinsic to the chain. Args: netuid (int): The subnet to register on. wallet (bittensor.wallet): The wallet to register. pow_result (POWSolution): The PoW result to register. wait_for_inclusion (bool): If ``True``, waits for the extrinsic to be included in a block. Default to `False`. wait_for_finalization (bool): If ``True``, waits for the extrinsic to be finalized. Default to `True`. Returns: success (bool): ``True`` if the extrinsic was included in a block. error (Optional[str]): ``None`` on success or not waiting for inclusion/finalization, otherwise the error message. """ @retry(delay=1, tries=3, backoff=2, max_delay=4) def make_substrate_call_with_retry(): # create extrinsic call call = self.substrate.compose_call( call_module="SubtensorModule", call_function="register", call_params={ "netuid": netuid, "block_number": pow_result.block_number, "nonce": pow_result.nonce, "work": [int(byte_) for byte_ in pow_result.seal], "hotkey": wallet.hotkey.ss58_address, "coldkey": wallet.coldkeypub.ss58_address, }, ) extrinsic = self.substrate.create_signed_extrinsic( call=call, keypair=wallet.hotkey ) response = self.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: return True, None # process if registration successful, try again if pow is still valid response.process_events() if not response.is_success: return False, format_error_message(response.error_message) # Successful registration else: return True, None return make_substrate_call_with_retry()
[docs] def register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, max_allowed_attempts: int = 3, output_in_place: bool = True, cuda: bool = False, dev_id: Union[list[int], int] = 0, tpb: int = 256, num_processes: Optional[int] = None, update_interval: Optional[int] = None, log_verbose: bool = False, ) -> bool: """Registers the wallet to the chain. Args: subtensor (bittensor.core.subtensor.Subtensor): Subtensor interface. wallet (bittensor.wallet): Bittensor wallet object. netuid (int): The ``netuid`` of the subnet to register on. 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. max_allowed_attempts (int): Maximum number of attempts to register the wallet. output_in_place (bool): If true, prints the progress of the proof of work to the console in-place. Meaning the progress is printed on the same lines. Defaults to `True`. cuda (bool): If ``true``, the wallet should be registered using CUDA device(s). dev_id (Union[List[int], int]): The CUDA device id to use, or a list of device ids. 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. log_verbose (bool): If ``true``, the registration process will log more information. 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``. """ if not subtensor.subnet_exists(netuid): logging.error( f":cross_mark: <red>Failed: </red> Subnet <blue>{netuid}</blue> does not exist." ) return False logging.info( f":satellite: <magenta>Checking Account on subnet</magenta> <blue>{netuid}</blue><magenta>...</magenta>" ) neuron = subtensor.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid ) if not neuron.is_null: logging.debug( f"Wallet <green>{wallet}</green> is already registered on <blue>{neuron.netuid}</blue> with <blue>{neuron.uid}</blue>." ) return True logging.debug( f"Registration hotkey: <blue>{wallet.hotkey.ss58_address}</blue>, <green>Public</green> coldkey: <blue>{wallet.coldkey.ss58_address}</blue> in the network: <blue>{subtensor.network}</blue>." ) if not torch: log_no_torch_error() return False # Attempt rolling registration. attempts = 1 while True: logging.info( f":satellite: <magenta>Registering...</magenta> <blue>({attempts}/{max_allowed_attempts})</blue>" ) # Solve latest POW. if cuda: if not torch.cuda.is_available(): return False pow_result: Optional[POWSolution] = create_pow( subtensor, wallet, netuid, output_in_place, cuda=cuda, dev_id=dev_id, tpb=tpb, num_processes=num_processes, update_interval=update_interval, log_verbose=log_verbose, ) else: pow_result: Optional[POWSolution] = create_pow( subtensor, wallet, netuid, output_in_place, cuda=cuda, num_processes=num_processes, update_interval=update_interval, log_verbose=log_verbose, ) # pow failed if not pow_result: # might be registered already on this subnet is_registered = subtensor.is_hotkey_registered( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: logging.info( f":white_heavy_check_mark: <green>Already registered on netuid:</green> <blue>{netuid}</blue>." ) return True # pow successful, proceed to submit pow to chain for registration else: logging.info(":satellite: <magenta>Submitting POW...</magenta>") # check if pow result is still valid while not pow_result.is_stale(subtensor=subtensor): result: tuple[bool, Optional[str]] = _do_pow_register( self=subtensor, netuid=netuid, wallet=wallet, pow_result=pow_result, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) success, err_msg = result if not success: # Look error here # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs if "HotKeyAlreadyRegisteredInSubNet" in err_msg: logging.info( f":white_heavy_check_mark: <green>Already Registered on subnet </green><blue>{netuid}</blue>." ) return True logging.error(f":cross_mark: <red>Failed:</red> {err_msg}") time.sleep(0.5) # Successful registration, final check for neuron and pubkey else: logging.info(":satellite: <magenta>Checking Balance...</magenta>") is_registered = subtensor.is_hotkey_registered( hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, ) if is_registered: logging.info( ":white_heavy_check_mark: <green>Registered</green>" ) return True else: # neuron not found, try again logging.error( ":cross_mark: <red>Unknown error. Neuron not found.</red>" ) continue else: # Exited loop because pow is no longer valid. logging.error("<red>POW is stale.</red>") # Try again. continue if attempts < max_allowed_attempts: # Failed registration, retry pow attempts += 1 logging.info( f":satellite: <magenta>Failed registration, retrying pow ...</magenta> <blue>({attempts}/{max_allowed_attempts})</blue>" ) else: # Failed to register after max attempts. logging.error("<red>No more attempts.</red>") return False
[docs] @ensure_connected def _do_burned_register( self, netuid: int, wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> tuple[bool, Optional[str]]: """ Performs a burned register extrinsic call to the Subtensor chain. This method sends a registration transaction to the Subtensor blockchain using the burned register mechanism. It retries the call up to three times with exponential backoff in case of failures. Args: self (bittensor.core.subtensor.Subtensor): Subtensor instance. netuid (int): The network unique identifier to register on. wallet (bittensor_wallet.Wallet): The wallet to be registered. wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is False. wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is True. Returns: Tuple[bool, Optional[str]]: A tuple containing a boolean indicating success or failure, and an optional error message. """ @retry(delay=1, tries=3, backoff=2, max_delay=4) def make_substrate_call_with_retry(): # create extrinsic call call = self.substrate.compose_call( call_module="SubtensorModule", call_function="burned_register", call_params={ "netuid": netuid, "hotkey": wallet.hotkey.ss58_address, }, ) extrinsic = self.substrate.create_signed_extrinsic( call=call, keypair=wallet.coldkey ) response = self.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: return True, None # process if registration successful, try again if pow is still valid response.process_events() if not response.is_success: return False, format_error_message(response.error_message) # Successful registration else: return True, None return make_substrate_call_with_retry()
[docs] def burned_register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> bool: """Registers the wallet to chain by recycling TAO. Args: subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance. wallet (bittensor.wallet): Bittensor wallet object. netuid (int): The ``netuid`` of the subnet to register on. 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. 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``. """ if not subtensor.subnet_exists(netuid): logging.error( f":cross_mark: <red>Failed error:</red> subnet <blue>{netuid}</blue> does not exist." ) return False try: wallet.unlock_coldkey() except KeyFileError: logging.error( ":cross_mark: <red>Keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid.</red>" ) return False logging.info( f":satellite: <magenta>Checking Account on subnet</magenta> <blue>{netuid}</blue><magenta> ...</magenta>" ) neuron = subtensor.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid ) old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) if not neuron.is_null: logging.info(":white_heavy_check_mark: <green>Already Registered</green>") logging.info(f"\t\tuid: <blue>{neuron.uid}</blue>") logging.info(f"\t\tnetuid: <blue>{neuron.netuid}</blue>") logging.info(f"\t\thotkey: <blue>{neuron.hotkey}</blue>") logging.info(f"\t\tcoldkey: <blue>{neuron.coldkey}</blue>") return True logging.info(":satellite: <magenta>Recycling TAO for Registration...</magenta>") recycle_amount = subtensor.recycle(netuid=netuid) logging.info(f"Recycling {recycle_amount} to register on subnet:{netuid}") success, err_msg = _do_burned_register( self=subtensor, netuid=netuid, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) if not success: logging.error(f":cross_mark: <red>Failed error:</red> {err_msg}") time.sleep(0.5) return False # Successful registration, final check for neuron and pubkey else: logging.info(":satellite: <magenta>Checking Balance...</magenta>") block = subtensor.get_current_block() new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) logging.info( f"Balance: <blue>{old_balance}</blue> :arrow_right: <green>{new_balance}</green>" ) is_registered = subtensor.is_hotkey_registered( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: logging.info(":white_heavy_check_mark: <green>Registered</green>") return True else: # neuron not found, try again logging.error(":cross_mark: <red>Unknown error. Neuron not found.</red>") return False