# The MIT License (MIT)
# Copyright © 2021 Yuma Rao
# Copyright © 2023 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 List, Union, Optional, Tuple
from rich.prompt import Confirm
import bittensor
from bittensor.utils import format_error_message
from bittensor.utils.registration import (
POWSolution,
create_pow,
torch,
log_no_torch_error,
)
[docs]
def register_extrinsic(
subtensor: "bittensor.subtensor",
wallet: "bittensor.wallet",
netuid: int,
wait_for_inclusion: bool = False,
wait_for_finalization: bool = True,
prompt: bool = False,
max_allowed_attempts: int = 3,
output_in_place: bool = True,
cuda: bool = False,
dev_id: Union[List[int], int] = 0,
tpb: int = 256,
num_processes: Optional[int] = None,
update_interval: Optional[int] = None,
log_verbose: bool = False,
) -> bool:
r"""Registers the wallet to the chain.
Args:
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.
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 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):
bittensor.__console__.print(
":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format(
netuid
)
)
return False
with bittensor.__console__.status(
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..."
):
neuron = subtensor.get_neuron_for_pubkey_and_subnet(
wallet.hotkey.ss58_address, netuid=netuid
)
if not neuron.is_null:
bittensor.logging.debug(
f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}"
)
return True
if prompt:
if not Confirm.ask(
"Continue Registration?\n hotkey: [bold white]{}[/bold white]\n coldkey: [bold white]{}[/bold white]\n network: [bold white]{}[/bold white]".format(
wallet.hotkey.ss58_address,
wallet.coldkeypub.ss58_address,
subtensor.network,
)
):
return False
if not torch:
log_no_torch_error()
return False
# Attempt rolling registration.
attempts = 1
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__.print("CUDA is not 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:
bittensor.__console__.print(
f":white_heavy_check_mark: [green]Already registered on netuid:{netuid}[/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 not pow_result.is_stale(subtensor=subtensor):
result: Tuple[bool, Optional[str]] = subtensor._do_pow_register(
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:
bittensor.__console__.print(
f":white_heavy_check_mark: [green]Already Registered on [bold]subnet:{netuid}[/bold][/green]"
)
return True
bittensor.__console__.print(
f":cross_mark: [red]Failed[/red]: {err_msg}"
)
time.sleep(0.5)
# Successful registration, final check for neuron and pubkey
else:
bittensor.__console__.print(":satellite: Checking Balance...")
is_registered = subtensor.is_hotkey_registered(
netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address
)
if is_registered:
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]")
# Try again.
continue
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
[docs]
def burned_register_extrinsic(
subtensor: "bittensor.subtensor",
wallet: "bittensor.wallet",
netuid: int,
wait_for_inclusion: bool = False,
wait_for_finalization: bool = True,
prompt: bool = False,
) -> bool:
r"""Registers the wallet to chain by recycling TAO.
Args:
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.
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 uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``.
"""
if not subtensor.subnet_exists(netuid):
bittensor.__console__.print(
":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format(
netuid
)
)
return False
try:
wallet.coldkey # unlock coldkey
except bittensor.KeyFileError:
bittensor.__console__.print(
":cross_mark: [red]Keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid[/red]:[bold white]\n [/bold white]"
)
return False
with bittensor.__console__.status(
f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..."
):
neuron = subtensor.get_neuron_for_pubkey_and_subnet(
wallet.hotkey.ss58_address, netuid=netuid
)
old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address)
recycle_amount = subtensor.recycle(netuid=netuid)
if not neuron.is_null:
bittensor.__console__.print(
":white_heavy_check_mark: [green]Already Registered[/green]:\n"
"uid: [bold white]{}[/bold white]\n"
"netuid: [bold white]{}[/bold white]\n"
"hotkey: [bold white]{}[/bold white]\n"
"coldkey: [bold white]{}[/bold white]".format(
neuron.uid, neuron.netuid, neuron.hotkey, neuron.coldkey
)
)
return True
if prompt:
# Prompt user for confirmation.
if not Confirm.ask(f"Recycle {recycle_amount} to register on subnet:{netuid}?"):
return False
with bittensor.__console__.status(":satellite: Recycling TAO for Registration..."):
success, err_msg = subtensor._do_burned_register(
netuid=netuid,
wallet=wallet,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
)
if not success:
bittensor.__console__.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
time.sleep(0.5)
return False
# Successful registration, final check for neuron and pubkey
else:
bittensor.__console__.print(":satellite: Checking Balance...")
block = subtensor.get_current_block()
new_balance = subtensor.get_balance(
wallet.coldkeypub.ss58_address, block=block
)
bittensor.__console__.print(
"Balance:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format(
old_balance, new_balance
)
)
is_registered = subtensor.is_hotkey_registered(
netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address
)
if is_registered:
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
[docs]
class MaxSuccessException(Exception):
pass
[docs]
class MaxAttemptsException(Exception):
pass
[docs]
def run_faucet_extrinsic(
subtensor: "bittensor.subtensor",
wallet: "bittensor.wallet",
wait_for_inclusion: bool = False,
wait_for_finalization: bool = True,
prompt: bool = False,
max_allowed_attempts: int = 3,
output_in_place: bool = True,
cuda: bool = False,
dev_id: Union[List[int], int] = 0,
tpb: int = 256,
num_processes: Optional[int] = None,
update_interval: Optional[int] = None,
log_verbose: bool = False,
) -> Tuple[bool, str]:
r"""Runs a continual POW to get a faucet of TAO on the test net.
Args:
wallet (bittensor.wallet):
Bittensor wallet object.
prompt (bool):
If ``true``, the call waits for confirmation from the user before proceeding.
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.
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 prompt:
if not Confirm.ask(
"Run Faucet ?\n coldkey: [bold white]{}[/bold white]\n network: [bold white]{}[/bold white]".format(
wallet.coldkeypub.ss58_address,
subtensor.network,
)
):
return False, ""
if not torch:
log_no_torch_error()
return False, "Requires torch"
# Unlock coldkey
try:
wallet.coldkey
except bittensor.KeyFileError:
bittensor.__console__.print(
":cross_mark: [red]Keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid[/red]:[bold white]\n [/bold white]"
)
return False, ""
# Get previous balance.
old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address)
# Attempt rolling registration.
attempts = 1
successes = 1
while True:
try:
pow_result = None
while pow_result is None or pow_result.is_stale(subtensor=subtensor):
# Solve latest POW.
if cuda:
if not torch.cuda.is_available():
if prompt:
bittensor.__console__.print("CUDA is not available.")
return False, "CUDA is not available."
pow_result: Optional[POWSolution] = create_pow(
subtensor,
wallet,
-1,
output_in_place,
cuda=cuda,
dev_id=dev_id,
tpb=tpb,
num_processes=num_processes,
update_interval=update_interval,
log_verbose=log_verbose,
)
else:
pow_result: Optional[POWSolution] = create_pow(
subtensor,
wallet,
-1,
output_in_place,
cuda=cuda,
num_processes=num_processes,
update_interval=update_interval,
log_verbose=log_verbose,
)
call = subtensor.substrate.compose_call(
call_module="SubtensorModule",
call_function="faucet",
call_params={
"block_number": pow_result.block_number,
"nonce": pow_result.nonce,
"work": [int(byte_) for byte_ in pow_result.seal],
},
)
extrinsic = subtensor.substrate.create_signed_extrinsic(
call=call, keypair=wallet.coldkey
)
response = subtensor.substrate.submit_extrinsic(
extrinsic,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
)
# process if registration successful, try again if pow is still valid
response.process_events()
if not response.is_success:
bittensor.__console__.print(
f":cross_mark: [red]Failed[/red]: {format_error_message(response.error_message)}"
)
if attempts == max_allowed_attempts:
raise MaxAttemptsException
attempts += 1
# Wait a bit before trying again
time.sleep(1)
# Successful registration
else:
new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address)
bittensor.__console__.print(
f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]"
)
old_balance = new_balance
if successes == 3:
raise MaxSuccessException
attempts = 1 # Reset attempts on success
successes += 1
except KeyboardInterrupt:
return True, "Done"
except MaxSuccessException:
return True, f"Max successes reached: {3}"
except MaxAttemptsException:
return False, f"Max attempts reached: {max_allowed_attempts}"
[docs]
def swap_hotkey_extrinsic(
subtensor: "bittensor.subtensor",
wallet: "bittensor.wallet",
new_wallet: "bittensor.wallet",
wait_for_inclusion: bool = False,
wait_for_finalization: bool = True,
prompt: bool = False,
) -> bool:
try:
wallet.coldkey # unlock coldkey
except bittensor.KeyFileError:
bittensor.__console__.print(
":cross_mark: [red]Keyfile is corrupt, non-writable, non-readable or the password used to decrypt is invalid[/red]:[bold white]\n [/bold white]"
)
return False
if prompt:
# Prompt user for confirmation.
if not Confirm.ask(
f"Swap {wallet.hotkey} for new hotkey: {new_wallet.hotkey}?"
):
return False
with bittensor.__console__.status(":satellite: Swapping hotkeys..."):
success, err_msg = subtensor._do_swap_hotkey(
wallet=wallet,
new_wallet=new_wallet,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
)
if not success:
bittensor.__console__.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
time.sleep(0.5)
return False
else:
bittensor.__console__.print(
f"Hotkey {wallet.hotkey} swapped for new hotkey: {new_wallet.hotkey}"
)
return True