From 35ff00160c6b3cade474c2b62c1055f539c1a27f Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 19:54:16 -0700 Subject: [PATCH 01/23] add logging --- bittensor/utils/registration/async_pow.py | 4 +++- bittensor/utils/registration/pow.py | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bittensor/utils/registration/async_pow.py b/bittensor/utils/registration/async_pow.py index a6d1c67c99..aa45cf502d 100644 --- a/bittensor/utils/registration/async_pow.py +++ b/bittensor/utils/registration/async_pow.py @@ -7,7 +7,7 @@ from typing import Callable, Union, Optional, TYPE_CHECKING from bittensor.core.errors import SubstrateRequestException - +from bittensor.utils.btlogging import logging from bittensor.utils.registration.pow import ( get_cpu_count, update_curr_block, @@ -512,6 +512,7 @@ async def create_pow_async( raise ValueError(f"Subnet {netuid} does not exist") solution: Optional[POWSolution] if cuda: + logging.debug("Solve difficulty with CUDA.") solution = await _solve_for_difficulty_fast_cuda( subtensor=subtensor, wallet=wallet, @@ -523,6 +524,7 @@ async def create_pow_async( log_verbose=log_verbose, ) else: + logging.debug("Solve difficulty.") solution = await _solve_for_difficulty_fast( subtensor=subtensor, wallet=wallet, diff --git a/bittensor/utils/registration/pow.py b/bittensor/utils/registration/pow.py index dfbd9b8fc3..1fa4c23ee8 100644 --- a/bittensor/utils/registration/pow.py +++ b/bittensor/utils/registration/pow.py @@ -409,7 +409,7 @@ def _solve_for_nonce_block_cuda( ) if solution != -1: - # Check if solution is valid (i.e. not -1) + # Check if solution is valid (i.e., not -1) return POWSolution(solution, block_number, difficulty, seal) return None @@ -1156,9 +1156,10 @@ def create_pow( raise ValueError(f"Subnet {netuid} does not exist.") if cuda: + logging.debug("Solve difficulty with CUDA.") solution: Optional[POWSolution] = _solve_for_difficulty_fast_cuda( - subtensor, - wallet, + subtensor=subtensor, + wallet=wallet, netuid=netuid, output_in_place=output_in_place, dev_id=dev_id, @@ -1167,9 +1168,10 @@ def create_pow( log_verbose=log_verbose, ) else: + logging.debug("Solve difficulty.") solution: Optional[POWSolution] = _solve_for_difficulty_fast( - subtensor, - wallet, + subtensor=subtensor, + wallet=wallet, netuid=netuid, output_in_place=output_in_place, num_processes=num_processes, From d8d6c253fa2d7ee4c9c475eb6d560a3dcd42d5c9 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 19:54:46 -0700 Subject: [PATCH 02/23] add `get_caller_name` --- bittensor/utils/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 662193792d..c75a0b7936 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -510,5 +510,14 @@ def get_transfer_fn_params( def get_function_name() -> str: - """Returns the name of the calling function.""" + """Return the current function's name.""" return inspect.currentframe().f_back.f_code.co_name + + +def get_caller_name(depth: int = 2) -> str: + """Return the name of the caller function.""" + frame = inspect.currentframe() + for _ in range(depth): + if frame is not None: + frame = frame.f_back + return frame.f_code.co_name if frame else "unknown" From 3a8759800c5ca54b4e029e5859e39558a5726be8 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:15:04 -0700 Subject: [PATCH 03/23] improve extrinsics --- bittensor/core/extrinsics/asyncex/children.py | 83 +-- .../core/extrinsics/asyncex/liquidity.py | 197 +++--- .../core/extrinsics/asyncex/move_stake.py | 473 +++++++------ .../core/extrinsics/asyncex/registration.py | 616 ++++++++--------- bittensor/core/extrinsics/asyncex/root.py | 144 ++-- bittensor/core/extrinsics/asyncex/serving.py | 254 +++---- bittensor/core/extrinsics/asyncex/staking.py | 565 +++++++-------- .../core/extrinsics/asyncex/start_call.py | 50 +- bittensor/core/extrinsics/asyncex/sudo.py | 20 +- bittensor/core/extrinsics/asyncex/take.py | 114 +-- bittensor/core/extrinsics/asyncex/transfer.py | 192 +++--- .../core/extrinsics/asyncex/unstaking.py | 650 +++++++++--------- bittensor/core/extrinsics/asyncex/utils.py | 25 +- bittensor/core/extrinsics/asyncex/weights.py | 113 +-- bittensor/core/extrinsics/children.py | 105 +-- bittensor/core/extrinsics/liquidity.py | 197 +++--- bittensor/core/extrinsics/move_stake.py | 461 +++++++------ bittensor/core/extrinsics/registration.py | 600 ++++++++-------- bittensor/core/extrinsics/root.py | 149 ++-- bittensor/core/extrinsics/serving.py | 285 ++++---- bittensor/core/extrinsics/staking.py | 546 ++++++++------- bittensor/core/extrinsics/start_call.py | 44 +- bittensor/core/extrinsics/sudo.py | 19 +- bittensor/core/extrinsics/take.py | 113 +-- bittensor/core/extrinsics/transfer.py | 186 +++-- bittensor/core/extrinsics/unstaking.py | 641 ++++++++--------- bittensor/core/extrinsics/utils.py | 25 +- bittensor/core/extrinsics/weights.py | 113 +-- 28 files changed, 3411 insertions(+), 3569 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 5ed14b7929..f758d3e4cc 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import float_to_u64, unlock_key, get_function_name -from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.asyncex.utils import sudo_call_extrinsic +from bittensor.utils import float_to_u64 if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -12,7 +12,7 @@ async def set_children_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -26,7 +26,7 @@ async def set_children_extrinsic( Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: bittensor wallet instance. - hotkey: The ``SS58`` address of the neuron's hotkey. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -52,16 +52,13 @@ async def set_children_extrinsic( bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. """ - unlock = unlock_key(wallet, raise_error=raise_error) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - async with subtensor.substrate as substrate: - call = await substrate.compose_call( + call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="set_children", call_params={ @@ -72,7 +69,7 @@ async def set_children_extrinsic( ) for proportion, child_hotkey in children ], - "hotkey": hotkey, + "hotkey": hotkey_ss58, "netuid": netuid, }, ) @@ -84,17 +81,12 @@ async def set_children_extrinsic( raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: - return response - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) + async def root_set_pending_childkey_cooldown_extrinsic( subtensor: "AsyncSubtensor", @@ -122,39 +114,14 @@ async def root_set_pending_childkey_cooldown_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - async with subtensor.substrate as substrate: - call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="set_pending_childkey_cooldown", - call_params={"cooldown": cooldown}, - ) - - sudo_call = await substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={"call": call}, - ) - - response = await subtensor.sign_and_send_extrinsic( - call=sudo_call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: - return response - - return response + return await sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_module="SubtensorModule", + call_function="set_pending_childkey_cooldown", + call_params={"cooldown": cooldown}, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index 115d4ec99c..f655da7a21 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -1,9 +1,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import price_to_tick if TYPE_CHECKING: @@ -48,37 +46,37 @@ async def add_liquidity_extrinsic( Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + tick_low = price_to_tick(price_low.tao) + tick_high = price_to_tick(price_high.tao) + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="add_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "tick_low": tick_low, + "tick_high": tick_high, + "liquidity": liquidity.rao, + }, ) - tick_low = price_to_tick(price_low.tao) - tick_high = price_to_tick(price_high.tao) - - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="add_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "tick_low": tick_low, - "tick_high": tick_high, - "liquidity": liquidity.rao, - }, - ) - - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def modify_liquidity_extrinsic( @@ -115,33 +113,33 @@ async def modify_liquidity_extrinsic( Note: Modifying is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="modify_position", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + "liquidity_delta": liquidity_delta.rao, + }, ) - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="modify_position", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - "liquidity_delta": liquidity_delta.rao, - }, - ) - - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def remove_liquidity_extrinsic( @@ -176,32 +174,32 @@ async def remove_liquidity_extrinsic( Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="remove_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + }, ) - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="remove_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - }, - ) - - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def toggle_user_liquidity_extrinsic( @@ -231,24 +229,25 @@ async def toggle_user_liquidity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="toggle_user_liquidity", + call_params={"netuid": netuid, "enable": enable}, ) - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="toggle_user_liquidity", - call_params={"netuid": netuid, "enable": enable}, - ) - - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 525f780b0a..192b94e7e0 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -1,8 +1,7 @@ import asyncio -from typing import TYPE_CHECKING, Optional +from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import get_function_name from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -73,60 +72,16 @@ async def transfer_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - ) - if stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message) - - logging.info( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " - f"[blue]{destination_coldkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" - ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + amount.set_unit(netuid=origin_netuid) - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + # Check sufficient stake + stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, @@ -135,17 +90,69 @@ async def transfer_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + logging.debug( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " + f"[blue]{destination_coldkey_ss58}[/blue]" ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + logging.debug( + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" ) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="transfer_stake", + call_params={ + "destination_coldkey": destination_coldkey_ss58, + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, + ) + + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + return response + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def swap_stake_extrinsic( @@ -187,86 +194,18 @@ async def swap_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - ) - - if stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - if safe_swapping: - origin_pool, destination_pool = await asyncio.gather( - subtensor.subnet(netuid=origin_netuid), - subtensor.subnet(netuid=destination_netuid), - ) - swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.info( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.info( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + amount.set_unit(netuid=origin_netuid) - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( - subtensor, + # Check sufficient stake + stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( + subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, @@ -274,23 +213,105 @@ async def swap_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + if safe_swapping: + origin_pool, destination_pool = await asyncio.gather( + subtensor.subnet(netuid=origin_netuid), + subtensor.subnet(netuid=destination_netuid), + ) + swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) + + logging.debug( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } + ) + call_function = "swap_stake_limit" + else: + logging.debug( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" + ) + call_function = "swap_stake" + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, ) - return response + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + ) + + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response - if safe_swapping and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + if safe_swapping and "Custom error: 8" in response.message: + response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." + + logging.error(f"[red]{response.message}[/red]") + return response - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def move_stake_extrinsic( @@ -329,84 +350,98 @@ async def move_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not amount and not move_all_stake: - message = ( - "Please specify an `amount` or `move_all_stake` argument to move stake." - ) - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check sufficient stake - stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey_ss58, - destination_hotkey_ss58=destination_hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - if move_all_stake: - amount = stake_in_origin - - elif stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - amount.set_unit(netuid=origin_netuid) - - logging.info( - f"Moving stake from hotkey [blue]{origin_hotkey_ss58}[/blue] to hotkey [blue]{destination_hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" - ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": origin_hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not amount and not move_all_stake: + return ExtrinsicResponse( + False, + "Please specify an `amount` or `move_all_stake` argument to move stake.", + ).with_log() + + # Check sufficient stake + stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=origin_hotkey_ss58, destination_hotkey_ss58=destination_hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, origin_netuid=origin_netuid, destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if move_all_stake: + amount = stake_in_origin + + elif stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + amount.set_unit(netuid=origin_netuid) + + logging.debug( + f"Moving stake from hotkey [blue]{origin_hotkey_ss58}[/blue] to hotkey [blue]{destination_hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="move_stake", + call_params={ + "origin_hotkey": origin_hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_hotkey": destination_hotkey_ss58, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, ) + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_hotkey_ss58=destination_hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index f3a29b1d42..7b455d087b 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -1,19 +1,14 @@ """ This module provides async functionalities for registering a wallet with the subtensor network using Proof-of-Work (PoW). - -Extrinsics: -- register_extrinsic: Registers the wallet to the subnet. -- burned_register_extrinsic: Registers the wallet to chain by recycling TAO. """ import asyncio from typing import Optional, Union, TYPE_CHECKING -from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee +from bittensor.core.errors import RegistrationError from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging -from bittensor.utils.registration import log_no_torch_error, create_pow_async, torch +from bittensor.utils.registration import create_pow_async, log_no_torch_error, torch if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -45,107 +40,98 @@ async def burned_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - block_hash = await subtensor.substrate.get_chain_head() - if not await subtensor.subnet_exists(netuid, block_hash=block_hash): - logging.error( - f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." - ) - return ExtrinsicResponse( - False, - f"Subnet #{netuid} does not exist", - extrinsic_function=get_function_name(), + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block_hash = await subtensor.substrate.get_chain_head() + if not await subtensor.subnet_exists(netuid, block_hash=block_hash): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() + + neuron, old_balance, recycle_amount = await asyncio.gather( + subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + subtensor.recycle(netuid=netuid, block_hash=block_hash), ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse( + message=message, data={"neuron": neuron, "old_balance": old_balance} + ) + + logging.debug(f"Recycling {recycle_amount} to register on subnet:{netuid}") + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + extrinsic_fee = response.extrinsic_fee + logging.debug( + f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{extrinsic_fee}[/blue]." ) + if not response.success: + logging.error(f"[red]{response.message}[/red]") + await asyncio.sleep(0.5) + return response - logging.info( - f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue][magenta] ...[/magenta]" - ) - - # We could do this as_completed because we don't need old_balance and recycle - # if neuron is null, but the complexity isn't worth it considering the small performance - # gains we'd hypothetically receive in this situation - neuron, old_balance, recycle_amount = await asyncio.gather( - subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash - ), - subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), - subtensor.recycle(netuid=netuid, block_hash=block_hash), - ) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/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 ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + # Successful registration, final check for neuron and pubkey + new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) - logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") - - # create extrinsic call - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - fee = await get_extrinsic_fee( - subtensor=subtensor, call=call, keypair=wallet.coldkeypub - ) - logging.info( - f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." - ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if not response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - await asyncio.sleep(0.5) - return response + response.data = { + "neuron": neuron, + "balance_before": old_balance, + "balance_after": new_balance, + "recycle_amount": recycle_amount, + } - # TODO: It is worth deleting everything below and simply returning the result without additional verification. This - # should be the responsibility of the user. We will also reduce the number of calls to the chain. - # Successful registration, final check for neuron and pubkey - logging.info(":satellite: [magenta]Checking Balance...[/magenta]") - block_hash = await subtensor.substrate.get_chain_head() - new_balance = await subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=block_hash - ) - - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = "Registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return response + if is_registered: + logging.debug("[green]Registered.[/green]") + return response + + # neuron not found + message = f"Neuron with hotkey {wallet.hotkey.ss58_address} not found in subnet {netuid} after registration." + return ExtrinsicResponse( + success=False, + message=message, + extrinsic=response.extrinsic, + error=RegistrationError(message), + ).with_log() - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - response.success = False - response.message = message - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def register_subnet_extrinsic( @@ -172,44 +158,50 @@ async def register_subnet_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - burn_cost = await subtensor.get_subnet_burn_cost() - - if burn_cost > balance: - message = f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + burn_cost = await subtensor.get_subnet_burn_cost() + + if burn_cost > balance: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO.", + ).with_log() + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + }, + ) - if response.success: - logging.success( - ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) + + if not wait_for_finalization and not wait_for_inclusion: + return response + + if response.success: + logging.debug("[green]Successfully registered subnet.[/green]") + return response + + logging.error(f"Failed to register subnet: {response.message}") return response - logging.error(f"Failed to register subnet: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def register_extrinsic( @@ -256,166 +248,149 @@ async def register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - block_hash = await subtensor.substrate.get_chain_head() - logging.debug("[magenta]Checking subnet status... [/magenta]") - if not await subtensor.subnet_exists(netuid, block_hash=block_hash): - message = f"Subnet #{netuid} does not exist." - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info( - f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue] [magenta]...[/magenta]" - ) - neuron = await subtensor.get_neuron_for_pubkey_and_subnet( - hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash - ) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/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 ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Registration hotkey: {wallet.hotkey.ss58_address}, Public coldkey: " - f"{wallet.coldkey.ss58_address} in the network: {subtensor.network}." - ) - - if not torch: - log_no_torch_error() - return ExtrinsicResponse( - False, "Torch is not installed.", extrinsic_function=get_function_name() - ) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block_hash = await subtensor.substrate.get_chain_head() + if not await subtensor.subnet_exists(netuid, block_hash=block_hash): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() - # Attempt rolling registration. - attempts = 1 + neuron = await subtensor.get_neuron_for_pubkey_and_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash + ) - while True: - logging.info( - f":satellite: [magenta]Registering...[/magenta] [blue]({attempts}/{max_allowed_attempts})[/blue]" + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse(message=message, data={"neuron": neuron}) + + logging.debug( + f"Registration hotkey: [blue]{wallet.hotkey.ss58_address}[/blue], Public coldkey: " + f"[blue]{wallet.coldkey.ss58_address}[/blue] in the network: [blue]{subtensor.network}[/blue]." ) - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - return ExtrinsicResponse( - False, "CUDA not available.", extrinsic_function=get_function_name() - ) - pow_result = await create_pow_async( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=output_in_place, - cuda=cuda, - dev_id=dev_id, - tpb=tpb, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - else: - pow_result = await create_pow_async( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=output_in_place, - cuda=cuda, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) + if not torch: + log_no_torch_error() + return ExtrinsicResponse(False, "Torch is not installed.").with_log() - # pow failed - if not pow_result: - # might be registered already on this subnet - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = f"Already registered on netuid: {netuid}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - True, message, extrinsic_function=get_function_name() - ) + # Attempt rolling registration. + attempts = 1 - # pow successful, proceed to submit pow to chain for registration - else: - logging.info(":satellite: [magenta]Submitting POW...[/magenta]") - # check if a pow result is still valid - while not await pow_result.is_stale_async(subtensor=subtensor): - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": netuid, - "block_number": pow_result.block_number, - "nonce": pow_result.nonce, - "work": [int(byte_) for byte_ in pow_result.seal], - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - }, + while True: + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + return ExtrinsicResponse(False, "CUDA not available.").with_log() + + logging.debug(f"Creating a POW with CUDA.") + pow_result = await create_pow_async( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + dev_id=dev_id, + tpb=tpb, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, + else: + logging.debug(f"Creating a POW.") + pow_result = await create_pow_async( + subtensor=subtensor, wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - if not response.success: - # Look error here - # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + # pow failed + if not pow_result: + # might be registered already on this subnet + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + message = f"Already registered in subnet {netuid}." + logging.debug(f"[green]{message}[/green]") + return ExtrinsicResponse(message=message) - if "HotKeyAlreadyRegisteredInSubNet" in response.message: - logging.info( - f":white_heavy_check_mark: [green]Already Registered on subnet:[/green] " - f"[blue]{netuid}[/blue]." - ) - return response - await asyncio.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - if response.success: - logging.info(":satellite: Checking Registration status...") - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + # pow successful, proceed to submit pow to chain for registration + else: + # check if a pow result is still valid + while not await pow_result.is_stale_async(subtensor=subtensor): + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register", + call_params={ + "netuid": netuid, + "block_number": pow_result.block_number, + "nonce": pow_result.nonce, + "work": [int(byte_) for byte_ in pow_result.seal], + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + }, ) - if is_registered: - logging.success( - ":white_heavy_check_mark: [green]Registered.[/green]" - ) - return response - - # neuron not found, try again - logging.error( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) - continue + + if not response.success: + # Look error here + # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + if "HotKeyAlreadyRegisteredInSubNet" in response.message: + logging.debug( + f"[green]Already registered on subnet:[/green] [blue]{netuid}[/blue]." + ) + return response + await asyncio.sleep(0.5) + + if response.success: + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + logging.debug("[green]Registered.[/green]") + return response + + # neuron not found, try again + logging.warning("[red]Unknown error. Neuron not found.[/red]") + continue + else: + # Exited loop because pow is no longer valid. + logging.warning("[red]POW is stale.[/red]") + # Try again. + + if attempts < max_allowed_attempts: + # Failed registration, retry pow + attempts += 1 + logging.warning( + f"Failed registration, retrying pow ... [blue]({attempts}/{max_allowed_attempts})[/blue]" + ) else: - # Exited loop because pow is no longer valid. - logging.error("[red]POW is stale.[/red]") - # Try again. - - if attempts < max_allowed_attempts: - # Failed registration, retry pow - attempts += 1 - logging.error( - f":satellite: [magenta]Failed registration, retrying pow ...[/magenta] " - f"[blue]({attempts}/{max_allowed_attempts})[/blue]" - ) - else: - # Failed to register after max attempts. - message = "No more attempts." - logging.error(f"[red]{message}[/red]") - return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) + # Failed to register after max attempts. + return ExtrinsicResponse(False, "No more attempts.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def set_subnet_identity_extrinsic( @@ -460,50 +435,51 @@ async def set_subnet_identity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_subnet_identity", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + "netuid": netuid, + "subnet_name": subnet_name, + "github_repo": github_repo, + "subnet_contact": subnet_contact, + "subnet_url": subnet_url, + "logo_url": logo_url, + "discord": discord, + "description": description, + "additional": additional, + }, + ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_subnet_identity", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "netuid": netuid, - "subnet_name": subnet_name, - "github_repo": github_repo, - "subnet_contact": subnet_contact, - "subnet_url": subnet_url, - "logo_url": logo_url, - "discord": discord, - "description": description, - "additional": additional, - }, - ) - - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + if not wait_for_finalization and not wait_for_inclusion: + return response - if response.success: - logging.success( - f":white_heavy_check_mark: [green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + if response.success: + logging.debug( + f"[green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + ) + return response + + logging.error( + f"[red]Failed to set identity for subnet {netuid}: {response.message}[/red]" ) return response - message = f"Failed to set identity for subnet #{netuid}" - logging.error(f":cross_mark: {message}: {response.message}") - response.message = message - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index 88a7f13c4d..cae2149052 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import u16_normalized_float, unlock_key, get_function_name +from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -59,85 +59,83 @@ async def root_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + netuid = 0 + logging.debug( + f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]." ) - netuid = 0 - logging.info( - f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]" - ) - - logging.info("Fetching recycle amount & balance.") - block_hash = await subtensor.get_block_hash() - recycle_call, balance = await asyncio.gather( - subtensor.get_hyperparameter( - param_name="Burn", - netuid=netuid, - block_hash=block_hash, - ), - subtensor.get_balance( - wallet.coldkeypub.ss58_address, - block_hash=block_hash, - ), - ) + block_hash = await subtensor.get_block_hash() + recycle_call, balance = await asyncio.gather( + subtensor.get_hyperparameter( + param_name="Burn", + netuid=netuid, + block_hash=block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, + block_hash=block_hash, + ), + ) - current_recycle = Balance.from_rao(int(recycle_call)) + current_recycle = Balance.from_rao(int(recycle_call)) - if balance < current_recycle: - message = f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO" - logging.error(f"[red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + if balance < current_recycle: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO.", + ).with_log() - logging.debug( - f"Checking if hotkey ([blue]{wallet.hotkey_str}[/blue]) is registered on root." - ) - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = "Already registered on root network." - logging.error(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + logging.debug( + f"Checking if hotkey ([blue]{wallet.hotkey.ss58_address}[/blue]) is registered on root." + ) + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) + if is_registered: + return ExtrinsicResponse(message="Already registered on root network.") - logging.info(":satellite: [magenta]Registering to root network...[/magenta]") - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="root_register", - call_params={"hotkey": wallet.hotkey.ss58_address}, - ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="root_register", + call_params={"hotkey": wallet.hotkey.ss58_address}, + ) + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) - if not response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - await asyncio.sleep(0.5) - return response + if not response.success: + logging.error(f"[red]{response.message}[/red]") + await asyncio.sleep(0.5) + return response - # Successful registration, final check for neuron and pubkey - uid = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="Uids", - params=[netuid, wallet.hotkey.ss58_address], - ) - if uid is not None: - response.data = {"uid": uid} - logging.info( - f":white_heavy_check_mark: [green]Registered with UID: {uid}[/green]." + # Successful registration, final check for neuron and pubkey + uid = await subtensor.get_uid_for_hotkey_on_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid ) - return response - - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + if uid is not None: + response.data = { + "hotkey_ss58": wallet.hotkey.ss58_address, + "netuid": netuid, + "uid": uid, + } + logging.debug( + f"Hotkey {wallet.hotkey.ss58_address} registered in subnet {netuid} with UID: {uid}." + ) + return response + + # neuron not found, try again + return ExtrinsicResponse(False, "Unknown error. Neuron not found.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 2ae86504da..88c05e63ea 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -3,12 +3,9 @@ from bittensor.core.errors import MetadataError from bittensor.core.settings import version_as_int -from bittensor.core.types import AxonServeCallParams -from bittensor.core.types import ExtrinsicResponse +from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse from bittensor.utils import ( - get_function_name, networking as net, - unlock_key, Certificate, ) from bittensor.utils.btlogging import logging @@ -57,73 +54,77 @@ async def serve_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - # Decrypt hotkey - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked + + params = AxonServeCallParams( + **{ + "version": version_as_int, + "ip": net.ip_to_int(ip), + "port": port, + "ip_type": net.ip_version(ip), + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + "protocol": protocol, + "placeholder1": placeholder1, + "placeholder2": placeholder2, + "certificate": certificate, + } ) + logging.debug("Checking axon ...") + neuron = await subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid + ) + neuron_up_to_date = not neuron.is_null and params == neuron + if neuron_up_to_date: + message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" + logging.debug(f"[blue]{message}[/blue]") + return ExtrinsicResponse(message=message) - params = AxonServeCallParams( - **{ - "version": version_as_int, - "ip": net.ip_to_int(ip), - "port": port, - "ip_type": net.ip_version(ip), - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - "protocol": protocol, - "placeholder1": placeholder1, - "placeholder2": placeholder2, - "certificate": certificate, - } - ) - logging.debug("Checking axon ...") - neuron = await subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid - ) - neuron_up_to_date = not neuron.is_null and params == neuron - if neuron_up_to_date: - message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" - logging.debug(f"[blue]{message}[/blue]") - return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " - f"[green]{subtensor.network}:{netuid}[/green]" - ) - - if params.certificate is None: - call_function = "serve_axon" - else: - call_function = "serve_axon_tls" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=params.dict(), - ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: logging.debug( - f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " + f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " f"[green]{subtensor.network}:{netuid}[/green]" ) + + if params.certificate is None: + call_function = "serve_axon" + else: + call_function = "serve_axon_tls" + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=params.dict(), + ) + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with=signing_keypair, + period=period, + raise_error=raise_error, + ) + + if response.success: + logging.debug( + f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " + f"[green]{subtensor.network}:{netuid}[/green]" + ) + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f"Failed: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def serve_axon_extrinsic( @@ -154,45 +155,57 @@ async def serve_axon_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(axon.wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + axon.wallet, raise_error, "hotkey" + ) + ).success: + return unlocked + + external_port = axon.external_port + + # ---- Get external ip ---- + if axon.external_ip is None: + try: + external_ip = await asyncio.get_running_loop().run_in_executor( + None, net.get_external_ip + ) + logging.debug( + f"[green]Found external ip:[/green] [blue]{external_ip}[/blue]" + ) + except Exception as error: + message = f"Unable to attain your external ip. Check your internet connection. Error: {error}" + if raise_error: + raise ConnectionError(message) from error + + return ExtrinsicResponse(False, message).with_log() + else: + external_ip = axon.external_ip + + # ---- Subscribe to chain ---- + response = await serve_extrinsic( + subtensor=subtensor, + wallet=axon.wallet, + ip=external_ip, + port=external_port, + protocol=4, + netuid=netuid, + certificate=certificate, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) + response.data = { + "external_ip": external_ip, + "external_port": external_port, + "axon": axon, + } + return response - external_port = axon.external_port - - # ---- Get external ip ---- - if axon.external_ip is None: - try: - external_ip = await asyncio.get_running_loop().run_in_executor( - None, net.get_external_ip - ) - logging.success( - f":white_heavy_check_mark: [green]Found external ip:[/green] [blue]{external_ip}[/blue]" - ) - except Exception as e: - raise ConnectionError( - f"Unable to attain your external ip. Check your internet connection. error: {e}" - ) from e - else: - external_ip = axon.external_ip - - # ---- Subscribe to chain ---- - response = await serve_extrinsic( - subtensor=subtensor, - wallet=axon.wallet, - ip=external_ip, - port=external_port, - protocol=4, - netuid=netuid, - certificate=certificate, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def publish_metadata_extrinsic( @@ -234,18 +247,20 @@ async def publish_metadata_extrinsic( MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates failure. """ - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked - fields = [{f"{data_type}": data}] - if reset_bonds: - fields.append({"ResetBondsFlag": b""}) + fields = [{f"{data_type}": data}] + if reset_bonds: + fields.append({"ResetBondsFlag": b""}) - async with subtensor.substrate as substrate: - call = await substrate.compose_call( + call = await subtensor.substrate.compose_call( call_module="Commitments", call_function="set_commitment", call_params={ @@ -257,23 +272,26 @@ async def publish_metadata_extrinsic( response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, - sign_with="hotkey", + sign_with=signing_keypair, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, - calling_function=get_function_name(), ) if response.success: return response + raise MetadataError(response.message) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) + async def get_metadata( subtensor: "AsyncSubtensor", netuid: int, - hotkey: str, + hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, @@ -286,7 +304,7 @@ async def get_metadata( commit_data = await subtensor.substrate.query( module="Commitments", storage_function="CommitmentOf", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -296,7 +314,7 @@ async def get_metadata( async def get_last_bonds_reset( subtensor: "AsyncSubtensor", netuid: int, - hotkey: str, + hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, @@ -307,7 +325,7 @@ async def get_last_bonds_reset( Parameters: subtensor: Subtensor instance object. netuid: The network uid to fetch from. - hotkey: The hotkey of the neuron for which to fetch the last bonds reset. + hotkey_ss58: The hotkey of the neuron for which to fetch the last bonds reset. block: The block number to query. If ``None``, the latest block is used. block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or reuse_block. reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. @@ -319,7 +337,7 @@ async def get_last_bonds_reset( block = await subtensor.substrate.query( module="Commitments", storage_function="LastBondsReset", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=block_hash, ) return block diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 9a6a78b5ca..9219c903c0 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -2,11 +2,9 @@ from typing import Optional, Sequence, TYPE_CHECKING from async_substrate_interface.errors import SubstrateRequestException -from bittensor.core.types import ExtrinsicResponse from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.utils import unlock_key, format_error_message, get_function_name -from bittensor.core.types import UIDs -from bittensor.utils import unlock_key, format_error_message +from bittensor.utils import format_error_message +from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -55,153 +53,146 @@ async def add_stake_extrinsic( Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. + + Notes: + The `data` field in the returned `ExtrinsicResponse` contains extra information about the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + block_hash = await subtensor.substrate.get_chain_head() - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - block_hash = await subtensor.substrate.get_chain_head() - - # Get current stake and existential deposit - old_stake, existential_deposit = await asyncio.gather( - subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=block_hash, - ), - subtensor.get_existential_deposit(block_hash=block_hash), - ) - - # Leave existential balance to keep key alive. - if amount > old_balance - existential_deposit: - # If we are staking all, we need to leave at least the existential deposit. - amount = old_balance - existential_deposit - else: - amount = amount - - # Check enough to stake. - if amount > old_balance: - message = "Not enough stake" - logging.error(f":cross_mark: [red]{message}:[/red]") - logging.error(f"\t\tbalance:{old_balance}") - logging.error(f"\t\tamount: {amount}") - logging.error(f"\t\twallet: {wallet.name}") - return ExtrinsicResponse( - False, f"{message}.", extrinsic_function=get_function_name() + # Get current stake and existential deposit + old_stake, existential_deposit = await asyncio.gather( + subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block_hash=block_hash, + ), + subtensor.get_existential_deposit(block_hash=block_hash), ) - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": amount.rao, - } + # Leave existential balance to keep key alive. + if amount > old_balance - existential_deposit: + # If we are staking all, we need to leave at least the existential deposit. + amount = old_balance - existential_deposit + else: + amount = amount - if safe_staking: - pool = await subtensor.subnet(netuid=netuid) - base_price = pool.price.tao + # Check enough to stake. + if amount > old_balance: + message = "Not enough stake" + logging.debug(f":cross_mark: [red]{message}:[/red]") + logging.debug(f"\t\tbalance:{old_balance}") + logging.debug(f"\t\tamount: {amount}") + logging.debug(f"\t\twallet: {wallet.name}") + return ExtrinsicResponse(False, f"{message}.").with_log() + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_staked": amount.rao, + } + + if safe_staking: + pool = await subtensor.subnet(netuid=netuid) + base_price = pool.price.tao + + price_with_tolerance = ( + base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + ) - price_with_tolerance = ( - base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) - ) + logging.debug( + f"Safe Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " + f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " + f"price limit: [green]{price_with_tolerance}[/green], " + f"original price: [green]{base_price}[/green], " + f"with partial stake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]." + ) - logging.info( - f":satellite: [magenta]Safe Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " - f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " - f"price limit: [green]{price_with_tolerance}[/green], " - f"original price: [green]{base_price}[/green], " - f"with partial stake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]" - ) + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } + ) + call_function = "add_stake_limit" + else: + logging.debug( + f"Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]." + ) + call_function = "add_stake" - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - call_function = "add_stake_limit" - else: - logging.info( - f":satellite: [magenta]Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]" + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, ) - call_function = "add_stake" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - if response.success: # If we successfully staked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return response + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + logging.debug("[green]Finalized.[/green]") + + new_block_hash = await subtensor.substrate.get_chain_head() + new_balance, new_stake = await asyncio.gather( + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=new_block_hash + ), + subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block_hash=new_block_hash, + ), + ) - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_stake, + } + return response - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] " - f"[blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - new_block_hash = await subtensor.substrate.get_chain_head() - new_balance, new_stake = await asyncio.gather( - subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=new_block_hash - ), - subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=new_block_hash, - ), - ) + if safe_staking and "Custom error: 8" in response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" - ) + logging.error(f"[red]{response.message}[/red]") return response - if safe_staking and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def add_stake_multiple_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -227,169 +218,193 @@ async def add_stake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. + + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `add_stake_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to either: + - the corresponding `ExtrinsicResponse` object if the staking attempt was executed, or + - `None` if the staking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the stake attempt. + + This allows the caller to inspect which specific operations were attempted and which were not. """ - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + ) - assert all( - [ - isinstance(netuids, list), - isinstance(hotkey_ss58s, list), - isinstance(amounts, list), - ] - ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + if len(hotkey_ss58s) == 0: + return ExtrinsicResponse(True, "Success") - if len(hotkey_ss58s) == 0: - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." + ) - assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( - "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." - ) + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("`hotkey_ss58s` must be a list of str.") - if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): - raise TypeError("hotkey_ss58s must be a list of str") + if not all(isinstance(a, Balance) for a in amounts): + raise TypeError("Each `amount` must be an instance of Balance.") - new_amounts: Sequence[Optional[Balance]] = [ - amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) - ] + new_amounts: Sequence[Optional[Balance]] = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] - if sum(amount.tao for amount in new_amounts) == 0: - # Staking 0 tao - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + if sum(amount.tao for amount in new_amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") + + block_hash = await subtensor.substrate.get_chain_head() + + all_stakes = await subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash + ) + old_stakes: list[Balance] = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, ) - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - block_hash = await subtensor.substrate.get_chain_head() - - all_stakes = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash - ) - old_stakes: list[Balance] = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - # 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 new_amounts] - ) - old_balance = initial_balance = await subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=block_hash - ) - - if total_staking_rao == 0: - # Staking all to the first wallet. - if old_balance.rao > 1000: - old_balance -= 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) - new_amounts = [ - Balance.from_tao(amount.tao * percent_reduction) for amount in new_amounts - ] + # 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 new_amounts] + ) + old_balance = initial_balance = await subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block_hash=block_hash + ) - successful_stakes = 0 - response = ExtrinsicResponse(False, "", extrinsic_function=get_function_name()) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, new_amounts, old_stakes, netuids) - ): - # Check enough to stake - if amount > old_balance: - logging.error( - f":cross_mark: [red]Not enough balance[/red]: [green]{old_balance}[/green] to stake: " - f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white]" - ) - continue + if total_staking_rao == 0: + # Staking all to the first wallet. + if old_balance.rao > 1000: + old_balance -= 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) + new_amounts = [ + Balance.from_tao(amount.tao * percent_reduction) + for amount in new_amounts + ] + + successful_stakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, new_amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # Check enough to stake + if amount > old_balance: + logging.warning( + f"Not enough balance: [green]{old_balance}[/green] to stake " + f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white] " + f"with hotkey: [blue]{hotkey_ss58}[/blue] on netuid [blue]{netuid}[/blue]." + ) + continue - try: - logging.info( - f"Staking [blue]{amount}[/blue] to hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " - f"[blue]{netuid}[/blue]" - ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": hotkey_ss58, - "amount_staked": amount.rao, - "netuid": netuid, - }, - ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + try: + logging.debug( + f"Staking [blue]{amount}[/blue] to hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + response = await add_stake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) - # If we successfully staked. - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - old_balance -= amount + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + old_balance -= amount + successful_stakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_block_hash = await subtensor.substrate.get_chain_head() + new_stake, new_balance = await asyncio.gather( + subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block_hash=new_block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=new_block_hash + ), + ) + logging.debug( + f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: " + f"[green]{new_stake}[/green]" + ) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + old_balance = new_balance successful_stakes += 1 continue - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - new_block_hash = await subtensor.substrate.get_chain_head() - new_stake, new_balance = await asyncio.gather( - subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=new_block_hash, - ), - subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=new_block_hash - ), - ) - logging.info( - f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: " - f"[green]{new_stake}[/green]" + logging.warning( + f"Staking amount {amount} to hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - old_balance = new_balance - successful_stakes += 1 - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - continue - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Add Stake Multiple error: {format_error_message(error)}[/red]" + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" + ) + if raise_error: + raise + + if len(netuids) > successful_stakes > 0: + success = False + message = "Some stake were successful." + elif successful_stakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one stake were successful." + + if ( + new_balance := await subtensor.get_balance(wallet.coldkeypub.ss58_address) + ) != initial_balance: + logging.debug( + f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + data.update( + {"balance_before": initial_balance, "balance_after": new_balance} ) - if successful_stakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/start_call.py b/bittensor/core/extrinsics/asyncex/start_call.py index d2a8747694..f40f8e77e1 100644 --- a/bittensor/core/extrinsics/asyncex/start_call.py +++ b/bittensor/core/extrinsics/asyncex/start_call.py @@ -1,8 +1,6 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -36,27 +34,27 @@ async def start_call_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - async with subtensor.substrate as substrate: - start_call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="start_call", - call_params={"netuid": netuid}, - ) - - response = await subtensor.sign_and_send_extrinsic( - call=start_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + async with subtensor.substrate as substrate: + start_call = await substrate.compose_call( + call_module="SubtensorModule", + call_function="start_call", + call_params={"netuid": netuid}, + ) + + return await subtensor.sign_and_send_extrinsic( + call=start_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/sudo.py b/bittensor/core/extrinsics/asyncex/sudo.py index b61cb10bfc..ec0bee9999 100644 --- a/bittensor/core/extrinsics/asyncex/sudo.py +++ b/bittensor/core/extrinsics/asyncex/sudo.py @@ -1,7 +1,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.extrinsics.asyncex.utils import sudo_call_extrinsic -from bittensor.core.types import Weights as MaybeSplit +from bittensor.core.types import Weights as MaybeSplit, ExtrinsicResponse from bittensor.utils.weight_utils import convert_maybe_split_to_u16 if TYPE_CHECKING: @@ -17,7 +17,7 @@ async def sudo_set_admin_freeze_window_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the admin freeze window length (in blocks) at the end of a tempo. @@ -33,9 +33,7 @@ async def sudo_set_admin_freeze_window_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_admin_freeze_window" call_params = {"window": window} @@ -60,7 +58,7 @@ async def sudo_set_mechanism_count_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the number of subnet mechanisms. @@ -77,9 +75,7 @@ async def sudo_set_mechanism_count_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_mechanism_count" call_params = {"netuid": netuid, "mechanism_count": mech_count} @@ -104,7 +100,7 @@ async def sudo_set_mechanism_emission_split_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the emission split between mechanisms in a provided subnet. @@ -121,9 +117,7 @@ async def sudo_set_mechanism_emission_split_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. Note: The `maybe_split` list defines the relative emission share for each subnet mechanism. diff --git a/bittensor/core/extrinsics/asyncex/take.py b/bittensor/core/extrinsics/asyncex/take.py index a60272461a..96416e18c2 100644 --- a/bittensor/core/extrinsics/asyncex/take.py +++ b/bittensor/core/extrinsics/asyncex/take.py @@ -1,20 +1,19 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Literal from bittensor_wallet.bittensor_wallet import Wallet from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor.core.async_subtensor import AsyncSubtensor -async def increase_take_extrinsic( +async def set_take_extrinsic( subtensor: "AsyncSubtensor", wallet: Wallet, hotkey_ss58: str, take: int, + action: Literal["increase_take", "decrease_take"], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -27,6 +26,7 @@ async def increase_take_extrinsic( wallet: The wallet to sign the extrinsic. hotkey_ss58: SS58 address of the hotkey to set take for. take: The percentage of rewards that the delegate claims from nominators. + action: The call function to use to set the take. Can be either "increase_take" or "decrease_take". period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. @@ -35,90 +35,30 @@ async def increase_take_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - Tuple[bool, str]: - - True and a success message if the extrinsic is successfully submitted or processed. - - False and an error message if the submission fails or the wallet cannot be unlocked. + ExtrinsicResponse: The result object of the extrinsic execution. """ - - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=action, + call_params={ + "hotkey": hotkey_ss58, + "take": take, + }, ) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - -async def decrease_take_extrinsic( - subtensor: "AsyncSubtensor", - wallet: Wallet, - hotkey_ss58: str, - take: int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """ - Sets the delegate 'take' percentage for a neuron identified by its hotkey. - - Parameters: - subtensor: The Subtensor instance. - wallet: The wallet to sign the extrinsic. - hotkey_ss58: SS58 address of the hotkey to set take for. - take: The percentage of rewards that the delegate claims from nominators. - period: The number of blocks during which the transaction will remain valid after it's submitted. If the - transaction is not included in a block within that number of blocks, it will expire and be rejected. You can - think of it as an expiration date for the transaction. - raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. - wait_for_inclusion: Whether to wait for the inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - Tuple[bool, str]: - - True and a success message if the extrinsic is successfully submitted or processed. - - False and an error message if the submission fails or the wallet cannot be unlocked. - """ - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), - ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index ce7639b9d8..f8dc6f8b49 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -1,13 +1,12 @@ import asyncio from typing import TYPE_CHECKING, Optional + +from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK from bittensor.core.types import ExtrinsicResponse -from bittensor.core.settings import NETWORK_EXPLORER_MAP from bittensor.utils import ( get_explorer_url_for_network, get_transfer_fn_params, is_valid_bittensor_address_or_public_key, - unlock_key, - get_function_name, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -48,104 +47,103 @@ async def transfer_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ - # Unlock wallet coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if amount is None and not transfer_all: + return ExtrinsicResponse( + False, "If not transferring all, `amount` must be specified." + ).with_log() + + # Validate destination address. + if not is_valid_bittensor_address_or_public_key(destination): + return ExtrinsicResponse( + False, f"Invalid destination SS58 address: {destination}" + ).with_log() + + # check existential deposit and fee + logging.debug("Fetching existential and fee.") + block_hash = await subtensor.substrate.get_chain_head() + old_balance, existential_deposit = await asyncio.gather( + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + subtensor.get_existential_deposit(block_hash=block_hash), ) - if amount is None and not transfer_all: - message = "If not transferring all, `amount` must be specified." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Validate destination address. - if not is_valid_bittensor_address_or_public_key(destination): - message = f"Invalid destination SS58 address: {destination}" - logging.error(f":cross_mark: [red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check balance. - logging.info( - f":satellite: [magenta]Checking balance and fees on chain [/magenta] [blue]{subtensor.network}[/blue]" - ) - # check existential deposit and fee - logging.debug("Fetching existential and fee") - block_hash = await subtensor.substrate.get_chain_head() - account_balance, existential_deposit = await asyncio.gather( - subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), - subtensor.get_existential_deposit(block_hash=block_hash), - ) - - fee = await subtensor.get_transfer_fee( - wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive - ) - - if not keep_alive: - # Check if the transfer should keep_alive the account - existential_deposit = Balance(0) - - # Check if we have enough balance. - if transfer_all is True: - if (account_balance - fee) < existential_deposit: - message = "Not enough balance to transfer." - logging.error(message) + fee = await subtensor.get_transfer_fee( + wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive + ) + + if not keep_alive: + # Check if the transfer should keep_alive the account + existential_deposit = Balance(0) + + # Check if we have enough balance. + if transfer_all: + if (old_balance - fee) < existential_deposit: + return ExtrinsicResponse( + False, "Not enough balance to transfer all stake." + ).with_log() + + elif old_balance < (amount + fee + existential_deposit): return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) - elif account_balance < (amount + fee + existential_deposit): - message = "Not enough balance." - logging.error(":cross_mark: [red]Not enough balance[/red]") - logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]") - logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]") - logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info(":satellite: [magenta]Transferring... old_stake: - message = f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {wallet.hotkey_str}" - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } - if safe_unstaking: - pool = await subtensor.subnet(netuid=netuid) - base_price = pool.price.tao - - if pool.netuid == 0: - price_with_tolerance = base_price - else: - price_with_tolerance = base_price * (1 - rate_tolerance) - - logging_info = ( - f":satellite: [magenta]Safe Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " - f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " - f"price limit: [green]{price_with_tolerance}[/green], " - f"original price: [green]{base_price}[/green], " - f"with partial unstake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" - else: - logging_info = ( - f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - call_function = "remove_stake" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - fee = await get_extrinsic_fee( - subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid - ) - logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]") - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_block_hash = await subtensor.substrate.get_chain_head() - new_balance, new_stake = await asyncio.gather( + block_hash = await subtensor.substrate.get_chain_head() + old_balance, old_stake = await asyncio.gather( subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=new_block_hash + address=wallet.coldkeypub.ss58_address, block_hash=block_hash ), subtensor.get_stake( coldkey_ss58=wallet.coldkeypub.ss58_address, hotkey_ss58=hotkey_ss58, netuid=netuid, - block_hash=new_block_hash, + block_hash=block_hash, ), ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + + amount.set_unit(netuid) + + # Check enough to unstake. + if amount > old_stake: + return ExtrinsicResponse( + False, + f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {hotkey_ss58}", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_unstaked": amount.rao, + } + if safe_unstaking: + pool = await subtensor.subnet(netuid=netuid) + base_price = pool.price.tao + + if pool.netuid == 0: + price_with_tolerance = base_price + else: + price_with_tolerance = base_price * (1 - rate_tolerance) + + logging_message = ( + f"Safe Unstaking from: " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " + f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " + f"price limit: [green]{price_with_tolerance}[/green], " + f"original price: [green]{base_price}[/green], " + f"with partial unstake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } + ) + call_function = "remove_stake_limit" + else: + logging_message = ( + f"Unstaking from: " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + call_function = "remove_stake" + + logging.debug(logging_message) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, ) + + if response.success: # If we successfully unstaked. + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + new_block_hash = await subtensor.substrate.get_chain_head() + new_balance, new_stake = await asyncio.gather( + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=new_block_hash + ), + subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block_hash=new_block_hash, + ), + ) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_stake, + } + return response + + if safe_unstaking and "Custom error: 8" in response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." + + logging.error(f"[red]{response.message}[/red]") return response - if safe_unstaking and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def unstake_all_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -199,7 +196,7 @@ async def unstake_all_extrinsic( Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. @@ -213,42 +210,43 @@ async def unstake_all_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - call_params = { - "hotkey": hotkey, - "netuid": netuid, - "limit_price": None, - } - - if rate_tolerance: - current_price = (await subtensor.subnet(netuid=netuid)).price - limit_price = current_price * (1 - rate_tolerance) - call_params.update({"limit_price": limit_price}) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "limit_price": None, + } + + if rate_tolerance: + current_price = (await subtensor.subnet(netuid=netuid)).price + limit_price = current_price * (1 - rate_tolerance) + call_params.update({"limit_price": limit_price}) + + async with subtensor.substrate as substrate: + call = await substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake_full_limit", + call_params=call_params, + ) - async with subtensor.substrate as substrate: - call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake_full_limit", - call_params=call_params, - ) + return await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def unstake_multiple_extrinsic( @@ -257,6 +255,7 @@ async def unstake_multiple_extrinsic( netuids: UIDs, hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, + rate_tolerance: Optional[float] = 0.05, unstake_all: bool = False, period: Optional[int] = None, raise_error: bool = False, @@ -272,6 +271,7 @@ async def unstake_multiple_extrinsic( netuids: List of subnets unique IDs to unstake from. hotkey_ss58s: List of hotkeys to unstake from. amounts: List of amounts to unstake. If ``None``, unstake all. + rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). unstake_all: If true, unstakes all tokens. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can @@ -282,183 +282,203 @@ async def unstake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - """ - # Unlock coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - # or amounts or unstake_all (no both) - if amounts and unstake_all: - raise ValueError("Cannot specify both `amounts` and `unstake_all`.") - - if amounts is not None and not all( - isinstance(amount, Balance) for amount in amounts - ): - raise TypeError("amounts must be a [list of bittensor.Balance] or None") - - if amounts is None: - amounts = [None] * len(hotkey_ss58s) - else: - # Convert to Balance - amounts = [amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids)] - if sum(amount.tao for amount in amounts) == 0: - # Staking 0 tao - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `unstake_extrinsic` or `unstake_all_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to + either: + - the corresponding `ExtrinsicResponse` object if the unstaking attempt was executed, or + - `None` if the unstaking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the unstake attempt. This allows the caller to inspect which specific operations + were attempted and which were not. + """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + # or amounts or unstake_all (no both) + if amounts and unstake_all: + raise ValueError("Cannot specify both `amounts` and `unstake_all`.") + + if amounts is not None and not all( + isinstance(amount, Balance) for amount in amounts + ): + raise TypeError("`amounts` must be a list of Balance or None.") + + if amounts is None: + amounts = [None] * len(hotkey_ss58s) + else: + # Convert to Balance + amounts = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." ) - assert all( - [ - isinstance(netuids, list), - isinstance(hotkey_ss58s, list), - isinstance(amounts, list), - ] - ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." - - if len(hotkey_ss58s) == 0: - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if len(hotkey_ss58s) == 0: + return ExtrinsicResponse(True, "Success") - assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( - "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." - ) - - if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): - raise TypeError("hotkey_ss58s must be a list of str") - - if amounts is not None and len(amounts) != len(hotkey_ss58s): - raise ValueError("amounts must be a list of the same length as hotkey_ss58s") - - if netuids is not None and len(netuids) != len(hotkey_ss58s): - raise ValueError("netuids must be a list of the same length as hotkey_ss58s") - - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - - block_hash = await subtensor.substrate.get_chain_head() - - all_stakes, old_balance = await asyncio.gather( - subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash - ), - subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), - ) - - old_stakes: list[Balance] = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - successful_unstakes = 0 - response = ExtrinsicResponse( - False, "Failed", extrinsic_function=get_function_name() - ) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, amounts, old_stakes, netuids) - ): - # Convert to bittensor.Balance - if amount is None: - # Unstake it all. - unstaking_balance = old_stake - logging.warning( - f"Didn't receive any unstaking amount. Unstaking all existing stake: [blue]{old_stake}[/blue] " - f"from hotkey: [blue]{hotkey_ss58}[/blue]" + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." ) - else: - unstaking_balance = amount - # Check enough to unstake. - stake_on_uid = old_stake - if unstaking_balance > stake_on_uid: - logging.error( - f":cross_mark: [red]Not enough stake[/red]: [green]{stake_on_uid}[/green] to unstake: " - f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{wallet.hotkey_str}[/blue]." - ) - continue + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("`hotkey_ss58s` must be a list of str.") - try: - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": hotkey_ss58, - "amount_unstaked": unstaking_balance.rao, - "netuid": netuid, - }, - ) - fee = await get_extrinsic_fee( - subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid - ) - logging.info( - f"Unstaking [blue]{unstaking_balance}[/blue] from hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " - f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]" + if amounts is not None and len(amounts) != len(hotkey_ss58s): + raise ValueError( + "`amounts` must be a list of the same length as `hotkey_ss58s`." ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), + if netuids is not None and len(netuids) != len(hotkey_ss58s): + raise ValueError( + "`netuids` must be a list of the same length as `hotkey_ss58s`." ) - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. + block_hash = await subtensor.substrate.get_chain_head() + all_stakes, old_balance = await asyncio.gather( + subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + ) + old_stakes = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, + ) + + successful_unstakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # Convert to bittensor.Balance + if amount is None: + # Unstake it all. + unstaking_balance = old_stake + logging.warning( + f"Didn't receive any unstaking amount. Unstaking all existing stake: [blue]{old_stake}[/blue] " + f"from hotkey: [blue]{hotkey_ss58}[/blue]" + ) + else: + unstaking_balance = amount + unstaking_balance.set_unit(netuid) + + # Check enough to unstake. + if unstaking_balance > old_stake: + logging.warning( + f"[red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " + f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{hotkey_ss58}[/blue]." + ) + continue - if not wait_for_finalization and not wait_for_inclusion: + try: + logging.debug( + f"Unstaking [blue]{amount}[/blue] from hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + if unstake_all: + response = await unstake_all_extrinsic( + subtensor=subtensor, + wallet=wallet, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + rate_tolerance=rate_tolerance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + else: + response = await unstake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=unstaking_balance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + successful_unstakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_stake = await subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + ) + logging.debug( + f"Stake ({hotkey_ss58}) in subnet {netuid}: " + f"[blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]." + ) successful_unstakes += 1 continue - logging.info(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]..." + logging.warning( + f"Unstaking from hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - block_hash = await subtensor.substrate.get_chain_head() - new_stake = await subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=block_hash, - ) - logging.info( - f"Stake ({hotkey_ss58}): [blue]{stake_on_uid}[/blue] :arrow_right: [green]{new_stake}[/green]" + + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" ) - successful_unstakes += 1 - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - continue + if raise_error: + raise + + if len(netuids) > successful_unstakes > 0: + success = False + message = "Some unstake were successful." + elif successful_unstakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one unstake were successful." - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Multiple unstake filed with error: {format_error_message(error)}[/red]" + if ( + new_balance := await subtensor.get_balance( + address=wallet.coldkeypub.ss58_address ) - if raise_error: - raise error - return response + ) != old_balance: + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + data.update({"balance_before": old_balance, "balance_after": new_balance}) - if successful_unstakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - block_hash = await subtensor.substrate.get_chain_head() - new_balance = await subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=block_hash - ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index 407c80c024..14665807ad 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -1,8 +1,7 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from scalecodec import GenericCall @@ -50,7 +49,7 @@ async def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Execute a sudo call extrinsic. Parameters: @@ -70,15 +69,16 @@ async def sudo_call_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return False, unlock.message + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type=sign_with + ) + ).success: + return unlocked + sudo_call = await subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", @@ -103,7 +103,4 @@ async def sudo_call_extrinsic( ) except Exception as error: - if raise_error: - raise error - - return False, str(error) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index a66c8e6213..e0bda0effa 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -6,12 +6,7 @@ from bittensor.core.settings import version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights -from bittensor.utils import ( - format_error_message, - get_function_name, - get_mechid_storage_index, - unlock_key, -) +from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -61,14 +56,15 @@ async def commit_timelocked_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) current_block = await subtensor.block @@ -111,8 +107,8 @@ async def commit_timelocked_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -128,16 +124,7 @@ async def commit_timelocked_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def commit_weights_extrinsic( @@ -176,13 +163,13 @@ async def commit_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights @@ -211,8 +198,8 @@ async def commit_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -224,16 +211,7 @@ async def commit_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def reveal_weights_extrinsic( @@ -273,14 +251,15 @@ async def reveal_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = await subtensor.substrate.compose_call( @@ -302,8 +281,8 @@ async def reveal_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -315,16 +294,7 @@ async def reveal_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def set_weights_extrinsic( @@ -362,15 +332,15 @@ async def set_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked - # Convert, reformat and normalize. + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = await subtensor.substrate.compose_call( @@ -391,26 +361,17 @@ async def set_weights_extrinsic( wait_for_finalization=wait_for_finalization, period=period, use_nonce=True, - nonce_key="hotkey", - sign_with="hotkey", + nonce_key=signing_keypair, + sign_with=signing_keypair, raise_error=raise_error, ) if response.success: - logging.debug("Successfully set weights and Finalized.") + logging.debug(response.message) return response logging.error(response.message) return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index f18a91ee52..1d81ea0a8a 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import float_to_u64, unlock_key, get_function_name -from bittensor.utils.btlogging import logging +from bittensor.utils import float_to_u64 +from bittensor.core.extrinsics.utils import sudo_call_extrinsic if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -12,7 +12,7 @@ def set_children_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -26,7 +26,7 @@ def set_children_extrinsic( Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: bittensor wallet instance. - hotkey: The ``SS58`` address of the neuron's hotkey. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -52,46 +52,40 @@ def set_children_extrinsic( bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. """ - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_children", + call_params={ + "children": [ + ( + float_to_u64(proportion), + child_hotkey, + ) + for proportion, child_hotkey in children + ], + "hotkey": hotkey_ss58, + "netuid": netuid, + }, ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - float_to_u64(proportion), - child_hotkey, - ) - for proportion, child_hotkey in children - ], - "hotkey": hotkey, - "netuid": netuid, - }, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) return response - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def root_set_pending_childkey_cooldown_extrinsic( @@ -120,39 +114,14 @@ def root_set_pending_childkey_cooldown_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - unlock = unlock_key(wallet) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - call = subtensor.substrate.compose_call( + return sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, call_module="SubtensorModule", call_function="set_pending_childkey_cooldown", call_params={"cooldown": cooldown}, - ) - - sudo_call = subtensor.substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={"call": call}, - ) - - response = subtensor.sign_and_send_extrinsic( - call=sudo_call, - wallet=wallet, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: - return response - - return response diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index 97efc27bcb..64ebc8c674 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -1,9 +1,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import price_to_tick if TYPE_CHECKING: @@ -48,37 +46,37 @@ def add_liquidity_extrinsic( Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + tick_low = price_to_tick(price_low.tao) + tick_high = price_to_tick(price_high.tao) + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="add_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "tick_low": tick_low, + "tick_high": tick_high, + "liquidity": liquidity.rao, + }, ) - tick_low = price_to_tick(price_low.tao) - tick_high = price_to_tick(price_high.tao) - - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="add_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "tick_low": tick_low, - "tick_high": tick_high, - "liquidity": liquidity.rao, - }, - ) - - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def modify_liquidity_extrinsic( @@ -115,33 +113,33 @@ def modify_liquidity_extrinsic( Note: Modifying is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="modify_position", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + "liquidity_delta": liquidity_delta.rao, + }, ) - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="modify_position", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - "liquidity_delta": liquidity_delta.rao, - }, - ) - - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def remove_liquidity_extrinsic( @@ -176,32 +174,32 @@ def remove_liquidity_extrinsic( Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="remove_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + }, ) - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="remove_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - }, - ) - - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def toggle_user_liquidity_extrinsic( @@ -231,24 +229,25 @@ def toggle_user_liquidity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="toggle_user_liquidity", + call_params={"netuid": netuid, "enable": enable}, ) - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="toggle_user_liquidity", - call_params={"netuid": netuid, "enable": enable}, - ) - - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index fc5fe29bf3..610c4c218a 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -3,7 +3,6 @@ from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from bittensor.utils import get_function_name if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -70,60 +69,16 @@ def transfer_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( - subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, - ) - if stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey [" - f"blue]{destination_coldkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" - ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + amount.set_unit(netuid=origin_netuid) - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( + # Check sufficient stake + stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, @@ -132,17 +87,69 @@ def transfer_stake_extrinsic( origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=destination_coldkey_ss58, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + logging.debug( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " + f"[blue]{destination_coldkey_ss58}[/blue]" + ) + logging.debug( + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="transfer_stake", + call_params={ + "destination_coldkey": destination_coldkey_ss58, + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, ) + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def swap_stake_extrinsic( @@ -184,84 +191,16 @@ def swap_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - - if stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - if safe_swapping: - origin_pool = subtensor.subnet(netuid=origin_netuid) - destination_pool = subtensor.subnet(netuid=destination_netuid) - swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.info( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.info( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) + amount.set_unit(netuid=origin_netuid) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( + # Check sufficient stake + stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, @@ -270,23 +209,103 @@ def swap_stake_extrinsic( origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + if safe_swapping: + origin_pool = subtensor.subnet(netuid=origin_netuid) + destination_pool = subtensor.subnet(netuid=destination_netuid) + swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) + + logging.debug( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } + ) + call_function = "swap_stake_limit" + else: + logging.debug( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" + ) + call_function = "swap_stake" + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, ) - return response + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response - if safe_swapping and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + if safe_swapping and "Custom error: 8" in response.message: + response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - return response + logging.error(f"[red]{response.message}[/red]") + return response + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def move_stake_extrinsic( @@ -325,67 +344,20 @@ def move_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not amount and not move_all_stake: - message = ( - "Please specify an `amount` or `move_all_stake` argument to move stake." - ) - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check sufficient stake - stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey_ss58, - destination_hotkey_ss58=destination_hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - if move_all_stake: - amount = stake_in_origin - - elif stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - amount.set_unit(netuid=origin_netuid) - - logging.info( - f"Moving stake from hotkey [blue]{origin_hotkey_ss58}[/blue] to hotkey [blue]{destination_hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid [yellow]{destination_netuid}[/yellow]" - ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": origin_hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not amount and not move_all_stake: + return ExtrinsicResponse( + False, + "Please specify an `amount` or `move_all_stake` argument to move stake.", + ).with_log() + + # Check sufficient stake + stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=origin_hotkey_ss58, destination_hotkey_ss58=destination_hotkey_ss58, @@ -394,14 +366,75 @@ def move_stake_extrinsic( origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if move_all_stake: + amount = stake_in_origin + + elif stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + amount.set_unit(netuid=origin_netuid) + + logging.debug( + f"Moving stake from hotkey [blue]{origin_hotkey_ss58}[/blue] to hotkey [blue]{destination_hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid [yellow]{destination_netuid}[/yellow]" + ) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="move_stake", + call_params={ + "origin_hotkey": origin_hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_hotkey": destination_hotkey_ss58, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, ) + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_hotkey_ss58=destination_hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index f710ef96e0..351af9ecc1 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -1,17 +1,12 @@ """ This module provides sync functionalities for registering a wallet with the subtensor network using Proof-of-Work (PoW). - -Extrinsics: -- register_extrinsic: Registers the wallet to the subnet. -- burned_register_extrinsic: Registers the wallet to chain by recycling TAO. """ import time from typing import Optional, Union, TYPE_CHECKING -from bittensor.core.extrinsics.utils import get_extrinsic_fee +from bittensor.core.errors import RegistrationError from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging from bittensor.utils.registration import create_pow, log_no_torch_error, torch @@ -45,99 +40,95 @@ def burned_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - block = subtensor.get_current_block() - if not subtensor.subnet_exists(netuid, block=block): - logging.error( - f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." - ) - return ExtrinsicResponse( - False, - f"Subnet #{netuid} does not exist", - extrinsic_function=get_function_name(), + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block = subtensor.get_current_block() + if not subtensor.subnet_exists(netuid, block=block): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() + + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid, block=block ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) + + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse( + message=message, data={"neuron": neuron, "old_balance": old_balance} + ) + + recycle_amount = subtensor.recycle(netuid=netuid, block=block) + logging.debug(f"Recycling {recycle_amount} to register on subnet:{netuid}") + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, ) + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + extrinsic_fee = response.extrinsic_fee + logging.debug( + f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{extrinsic_fee}[/blue]." + ) + if not response.success: + logging.error(f"[red]{response.message}[/red]") + time.sleep(0.5) + return response - 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, block=block - ) - - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/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 ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + # Successful registration, final check for neuron and pubkey + new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + + logging.debug( + 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 ) - recycle_amount = subtensor.recycle(netuid=netuid, block=block) - logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") - logging.info(f"Recycling {recycle_amount} to register on subnet:{netuid}") - - # create extrinsic call - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - fee = get_extrinsic_fee(subtensor=subtensor, call=call, keypair=wallet.coldkeypub) - logging.info( - f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." - ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if not response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - time.sleep(0.5) - return response + response.data = { + "neuron": neuron, + "balance_before": old_balance, + "balance_after": new_balance, + "recycle_amount": recycle_amount, + } - # TODO: It is worth deleting everything below and simply returning the result without additional verification. This - # should be the responsibility of the user. We will also reduce the number of calls to the chain. - # Successful registration, final check for neuron and pubkey - 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, block=block - ) - if is_registered: - message = "Registered" - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return response + if is_registered: + logging.debug("[green]Registered.[/green]") + return response - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - response.success = False - response.message = message - return response + # neuron not found + message = f"Neuron with hotkey {wallet.hotkey.ss58_address} not found in subnet {netuid} after registration." + return ExtrinsicResponse( + success=False, + message=message, + extrinsic=response.extrinsic, + error=RegistrationError(message), + ).with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def register_subnet_extrinsic( @@ -164,44 +155,50 @@ def register_subnet_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - burn_cost = subtensor.get_subnet_burn_cost() - - if burn_cost > balance: - message = f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - if response.success: - logging.success( - ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" + balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + burn_cost = subtensor.get_subnet_burn_cost() + + if burn_cost > balance: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO.", + ).with_log() + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + }, + ) + + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) + + if not wait_for_finalization and not wait_for_inclusion: + return response + + if response.success: + logging.debug("[green]Successfully registered subnet.[/green]") + return response + + logging.error(f"Failed to register subnet: {response.message}") return response - logging.error(f"Failed to register subnet: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def register_extrinsic( @@ -245,168 +242,150 @@ def register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block = subtensor.get_current_block() + if not subtensor.subnet_exists(netuid, block=block): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() - logging.debug("[magenta]Checking subnet status... [/magenta]") - block = subtensor.get_current_block() - if not subtensor.subnet_exists(netuid, block=block): - message = f"Subnet #{netuid} does not exist." - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info( - f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue] [magenta]...[/magenta]" - ) - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block=block - ) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/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 ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Registration hotkey: {wallet.hotkey.ss58_address}, Public coldkey: " - f"{wallet.coldkey.ss58_address} in the network: {subtensor.network}." - ) - - if not torch: - log_no_torch_error() - return ExtrinsicResponse( - False, "No torch installed.", extrinsic_function=get_function_name() + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block=block ) - # Attempt rolling registration. - attempts = 1 - - while True: - logging.info( - f":satellite: [magenta]Registering...[/magenta] [blue]({attempts}/{max_allowed_attempts})[/blue]" + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse(message=message, data={"neuron": neuron}) + + logging.debug( + f"Registration hotkey: [blue]{wallet.hotkey.ss58_address}[/blue], Public coldkey: " + f"[blue]{wallet.coldkey.ss58_address}[/blue] in the network: [blue]{subtensor.network}[/blue]." ) - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - return ExtrinsicResponse( - False, "CUDA not available.", extrinsic_function=get_function_name() - ) - pow_result = create_pow( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=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 = create_pow( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=output_in_place, - cuda=cuda, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) + if not torch: + log_no_torch_error() + return ExtrinsicResponse(False, "Torch is not installed.").with_log() - # 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: - message = f"Already registered on netuid: {netuid}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - True, message, extrinsic_function=get_function_name() - ) + # Attempt rolling registration. + attempts = 1 + + while True: + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + return ExtrinsicResponse(False, "CUDA not available.").with_log() - # pow successful, proceed to submit pow to chain for registration - else: - logging.info(":satellite: [magenta]Submitting POW...[/magenta]") - # check if a pow result is still valid - while not pow_result.is_stale(subtensor=subtensor): - # create extrinsic call - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": netuid, - "block_number": pow_result.block_number, - "nonce": pow_result.nonce, - "work": [int(byte_) for byte_ in pow_result.seal], - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - }, + logging.debug(f"Creating a POW with CUDA.") + pow_result = create_pow( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + dev_id=dev_id, + tpb=tpb, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - response = subtensor.sign_and_send_extrinsic( - call=call, + else: + logging.debug(f"Creating a POW.") + pow_result = create_pow( + subtensor=subtensor, wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - if not response.success: - # Look error here - # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + # 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: + message = f"Already registered in subnet {netuid}." + logging.debug(f"[green]{message}[/green]") + return ExtrinsicResponse(message=message) - if "HotKeyAlreadyRegisteredInSubNet" in response.message: - logging.info( - f":white_heavy_check_mark: [green]Already Registered on subnet:[/green] " - f"[blue]{netuid}[/blue]." - ) - return response - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - if response.success: - logging.info(":satellite: Checking Registration status...") - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + # pow successful, proceed to submit pow to chain for registration + else: + # check if a pow result is still valid + while not pow_result.is_stale(subtensor=subtensor): + # create extrinsic call + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register", + call_params={ + "netuid": netuid, + "block_number": pow_result.block_number, + "nonce": pow_result.nonce, + "work": [int(byte_) for byte_ in pow_result.seal], + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + }, ) - if is_registered: - logging.success( - ":white_heavy_check_mark: [green]Registered.[/green]" + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if not response.success: + # Look error here + # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + if "HotKeyAlreadyRegisteredInSubNet" in response.message: + logging.debug( + f"[green]Already registered on subnet:[/green] [blue]{netuid}[/blue]." + ) + return response + time.sleep(0.5) + + if response.success: + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) - return response + if is_registered: + logging.debug("[green]Registered.[/green]") + return response # neuron not found, try again - logging.error( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" - ) - continue + logging.warning("[red]Unknown error. Neuron not found.[/red]") + continue + else: + # Exited loop because pow is no longer valid. + logging.warning("[red]POW is stale.[/red]") + # Try again. + + if attempts < max_allowed_attempts: + # Failed registration, retry pow + attempts += 1 + logging.warning( + f"Failed registration, retrying pow ... [blue]({attempts}/{max_allowed_attempts})[/blue]" + ) else: - # Exited loop because pow is no longer valid. - logging.error("[red]POW is stale.[/red]") - # Try again. - - if attempts < max_allowed_attempts: - # Failed registration, retry pow - attempts += 1 - logging.error( - f":satellite: [magenta]Failed registration, retrying pow ...[/magenta] " - f"[blue]({attempts}/{max_allowed_attempts})[/blue]" - ) - else: - # Failed to register after max attempts. - message = "No more attempts." - logging.error(f"[red]{message}[/red]") - return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) + # Failed to register after max attempts. + return ExtrinsicResponse(False, "No more attempts.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def set_subnet_identity_extrinsic( @@ -451,50 +430,51 @@ def set_subnet_identity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_subnet_identity", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + "netuid": netuid, + "subnet_name": subnet_name, + "github_repo": github_repo, + "subnet_contact": subnet_contact, + "subnet_url": subnet_url, + "logo_url": logo_url, + "discord": discord, + "description": description, + "additional": additional, + }, + ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_subnet_identity", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "netuid": netuid, - "subnet_name": subnet_name, - "github_repo": github_repo, - "subnet_contact": subnet_contact, - "subnet_url": subnet_url, - "logo_url": logo_url, - "discord": discord, - "description": description, - "additional": additional, - }, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + if not wait_for_finalization and not wait_for_inclusion: + return response - if response.success: - logging.success( - f":white_heavy_check_mark: [green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + if response.success: + logging.debug( + f"[green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + ) + return response + + logging.error( + f"[red]Failed to set identity for subnet {netuid}: {response.message}[/red]" ) return response - message = f"Failed to set identity for subnet #{netuid}" - logging.error(f":cross_mark: {message}: {response.message}") - response.message = message - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index 3458444b70..91f3753c16 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import u16_normalized_float, unlock_key, get_function_name +from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -57,84 +57,81 @@ def root_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + netuid = 0 + logging.debug( + f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]." ) - netuid = 0 - logging.info( - f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]" - ) - - logging.info("Fetching recycle amount & balance.") - block = subtensor.get_current_block() - recycle_call = subtensor.get_hyperparameter( - param_name="Burn", - netuid=netuid, - block=block, - ) - balance = subtensor.get_balance( - address=wallet.coldkeypub.ss58_address, - block=block, - ) - - current_recycle = Balance.from_rao(int(recycle_call)) - - if balance < current_recycle: - message = f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO" - logging.error(f"[red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Checking if hotkey ([blue]{wallet.hotkey_str}[/blue]) is registered on root." - ) - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = "Already registered on root network." - logging.error(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + block = subtensor.get_current_block() + recycle_call = subtensor.get_hyperparameter( + param_name="Burn", + netuid=netuid, + block=block, ) + balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, + block=block, + ) + + current_recycle = Balance.from_rao(int(recycle_call)) + + if balance < current_recycle: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO.", + ).with_log() - logging.info(":satellite: [magenta]Registering to root network...[/magenta]") - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="root_register", - call_params={"hotkey": wallet.hotkey.ss58_address}, - ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) - - if not response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - time.sleep(0.5) - return response - - # Successful registration, final check for neuron and pubkey - uid = subtensor.substrate.query( - module="SubtensorModule", - storage_function="Uids", - params=[netuid, wallet.hotkey.ss58_address], - ) - if uid is not None: - response.data = {"uid": uid} - logging.info( - f":white_heavy_check_mark: [green]Registered with UID: {uid}[/green]." + logging.debug( + f"Checking if hotkey ([blue]{wallet.hotkey.ss58_address}[/blue]) is registered on root." ) - return response + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + return ExtrinsicResponse(message="Already registered on root network.") - # neuron not found - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="root_register", + call_params={"hotkey": wallet.hotkey.ss58_address}, + ) + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if not response.success: + logging.error(f"[red]{response.message}[/red]") + time.sleep(0.5) + return response + + # Successful registration, final check for neuron and pubkey + uid = subtensor.get_uid_for_hotkey_on_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid + ) + if uid is not None: + response.data = { + "hotkey_ss58": wallet.hotkey.ss58_address, + "netuid": netuid, + "uid": uid, + } + logging.debug( + f"Hotkey {wallet.hotkey.ss58_address} registered in subnet {netuid} with UID: {uid}." + ) + return response + + # neuron not found, try again + return ExtrinsicResponse(False, "Unknown error. Neuron not found.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 554ba890ba..c1efae01f1 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -2,12 +2,9 @@ from bittensor.core.errors import MetadataError from bittensor.core.settings import version_as_int -from bittensor.core.types import AxonServeCallParams -from bittensor.core.types import ExtrinsicResponse +from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse from bittensor.utils import ( - get_function_name, networking as net, - unlock_key, Certificate, ) from bittensor.utils.btlogging import logging @@ -56,74 +53,78 @@ def serve_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - # Decrypt hotkey - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked + + params = AxonServeCallParams( + **{ + "version": version_as_int, + "ip": net.ip_to_int(ip), + "port": port, + "ip_type": net.ip_version(ip), + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + "protocol": protocol, + "placeholder1": placeholder1, + "placeholder2": placeholder2, + "certificate": certificate, + } ) + logging.debug("Checking axon ...") + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid + ) + neuron_up_to_date = not neuron.is_null and params == neuron + if neuron_up_to_date: + message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" + logging.debug(f"[blue]{message}[/blue]") + return ExtrinsicResponse(message=message) - params = AxonServeCallParams( - **{ - "version": version_as_int, - "ip": net.ip_to_int(ip), - "port": port, - "ip_type": net.ip_version(ip), - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - "protocol": protocol, - "placeholder1": placeholder1, - "placeholder2": placeholder2, - "certificate": certificate, - } - ) - logging.debug("Checking axon ...") - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid - ) - neuron_up_to_date = not neuron.is_null and params == neuron - if neuron_up_to_date: - message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" - logging.debug(f"[blue]{message}[/blue]") - return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " - f"[green]{subtensor.network}:{netuid}[/green]" - ) - - if params.certificate is None: - call_function = "serve_axon" - else: - call_function = "serve_axon_tls" + logging.debug( + f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " + f"[green]{subtensor.network}:{netuid}[/green]" + ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=params.dict(), - ) + if params.certificate is None: + call_function = "serve_axon" + else: + call_function = "serve_axon_tls" - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=params.dict(), + ) - if response.success: - logging.debug( - f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " - f"[green]{subtensor.network}:{netuid}[/green]" + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with=signing_keypair, + period=period, + raise_error=raise_error, ) + + if response.success: + logging.debug( + f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " + f"[green]{subtensor.network}:{netuid}[/green]" + ) + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f"Failed: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def serve_axon_extrinsic( @@ -154,43 +155,55 @@ def serve_axon_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(axon.wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + axon.wallet, raise_error, "hotkey" + ) + ).success: + return unlocked + + external_port = axon.external_port + + # ---- Get external ip ---- + if axon.external_ip is None: + try: + external_ip = net.get_external_ip() + logging.debug( + f"[green]Found external ip:[/green] [blue]{external_ip}[/blue]" + ) + except Exception as error: + message = f"Unable to attain your external ip. Check your internet connection. Error: {error}" + if raise_error: + raise ConnectionError(message) from error + + return ExtrinsicResponse(False, message).with_log() + else: + external_ip = axon.external_ip + + # ---- Subscribe to chain ---- + response = serve_extrinsic( + subtensor=subtensor, + wallet=axon.wallet, + ip=external_ip, + port=external_port, + protocol=4, + netuid=netuid, + certificate=certificate, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) + response.data = { + "external_ip": external_ip, + "external_port": external_port, + "axon": axon, + } + return response - external_port = axon.external_port - - # ---- Get external ip ---- - if axon.external_ip is None: - try: - external_ip = net.get_external_ip() - logging.success( - f":white_heavy_check_mark: [green]Found external ip:[/green] [blue]{external_ip}[/blue]" - ) - except Exception as e: - raise ConnectionError( - f"Unable to attain your external ip. Check your internet connection. error: {e}" - ) from e - else: - external_ip = axon.external_ip - - # ---- Subscribe to chain ---- - response = serve_extrinsic( - subtensor=subtensor, - wallet=axon.wallet, - ip=external_ip, - port=external_port, - protocol=4, - netuid=netuid, - certificate=certificate, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def publish_metadata_extrinsic( @@ -231,56 +244,62 @@ def publish_metadata_extrinsic( MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates failure. """ - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked + + fields = [{f"{data_type}": data}] + if reset_bonds: + fields.append({"ResetBondsFlag": b""}) + + call = subtensor.substrate.compose_call( + call_module="Commitments", + call_function="set_commitment", + call_params={ + "netuid": netuid, + "info": {"fields": [fields]}, + }, ) - fields = [{f"{data_type}": data}] - if reset_bonds: - fields.append({"ResetBondsFlag": b""}) - - call = subtensor.substrate.compose_call( - call_module="Commitments", - call_function="set_commitment", - call_params={ - "netuid": netuid, - "info": {"fields": [fields]}, - }, - ) + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + sign_with="hotkey", + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - sign_with="hotkey", - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + if response.success: + return response - if response.success: - return response - raise MetadataError(response.message) + raise MetadataError(response.message) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def get_metadata( - subtensor: "Subtensor", netuid: int, hotkey: str, block: Optional[int] = None + subtensor: "Subtensor", netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> Union[str, dict]: """Fetches metadata from the blockchain for a given hotkey and netuid.""" commit_data = subtensor.substrate.query( module="Commitments", storage_function="CommitmentOf", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=subtensor.determine_block_hash(block), ) return commit_data def get_last_bonds_reset( - subtensor: "Subtensor", netuid: int, hotkey: str, block: Optional[int] = None + subtensor: "Subtensor", netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> bytes: """ Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. @@ -288,7 +307,7 @@ def get_last_bonds_reset( Parameters: subtensor: Subtensor instance object. netuid: The network uid to fetch from. - hotkey: The hotkey of the neuron for which to fetch the last bonds reset. + hotkey_ss58: The hotkey of the neuron for which to fetch the last bonds reset. block: The block number to query. If ``None``, the latest block is used. Returns: @@ -297,6 +316,6 @@ def get_last_bonds_reset( return subtensor.substrate.query( module="Commitments", storage_function="LastBondsReset", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=subtensor.determine_block_hash(block), ) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 3399ec7dc5..949166a994 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -1,11 +1,10 @@ from typing import Optional, TYPE_CHECKING, Sequence from async_substrate_interface.errors import SubstrateRequestException -from bittensor.core.types import ExtrinsicResponse + from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.utils import unlock_key, format_error_message, get_function_name -from bittensor.core.types import UIDs -from bittensor.utils import unlock_key, format_error_message +from bittensor.core.types import ExtrinsicResponse, UIDs +from bittensor.utils import format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -54,148 +53,141 @@ def add_stake_extrinsic( Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. + + Notes: + The `data` field in the returned `ExtrinsicResponse` contains extra information about the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + block = subtensor.get_current_block() - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - block = subtensor.get_current_block() - - # Get current stake and existential deposit - old_stake = subtensor.get_stake( - hotkey_ss58=hotkey_ss58, - coldkey_ss58=wallet.coldkeypub.ss58_address, - netuid=netuid, - block=block, - ) - existential_deposit = subtensor.get_existential_deposit(block=block) - - # Leave existential balance to keep key alive. - if amount > old_balance - existential_deposit: - # If we are staking all, we need to leave at least the existential deposit. - amount = old_balance - existential_deposit - - # Check enough to stake. - if amount > old_balance: - message = "Not enough stake" - logging.error(f":cross_mark: [red]{message}:[/red]") - logging.error(f"\t\tbalance:{old_balance}") - logging.error(f"\t\tamount: {amount}") - logging.error(f"\t\twallet: {wallet.name}") - return ExtrinsicResponse( - False, f"{message}.", extrinsic_function=get_function_name() + # Get current stake and existential deposit + old_stake = subtensor.get_stake( + hotkey_ss58=hotkey_ss58, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=netuid, + block=block, ) + existential_deposit = subtensor.get_existential_deposit(block=block) - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": amount.rao, - } + # Leave existential balance to keep key alive. + if amount > old_balance - existential_deposit: + # If we are staking all, we need to leave at least the existential deposit. + amount = old_balance - existential_deposit - if safe_staking: - pool = subtensor.subnet(netuid=netuid) - base_price = pool.price.tao + # Check enough to stake. + if amount > old_balance: + message = "Not enough stake" + logging.debug(f":cross_mark: [red]{message}:[/red]") + logging.debug(f"\t\tbalance:{old_balance}") + logging.debug(f"\t\tamount: {amount}") + logging.debug(f"\t\twallet: {wallet.name}") + return ExtrinsicResponse(False, f"{message}.").with_log() + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_staked": amount.rao, + } + + if safe_staking: + pool = subtensor.subnet(netuid=netuid) + base_price = pool.price.tao + + price_with_tolerance = ( + base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + ) - price_with_tolerance = ( - base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + logging.debug( + f"Safe Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " + f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " + f"price limit: [green]{price_with_tolerance}[/green], " + f"original price: [green]{base_price}[/green], " + f"with partial stake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]." + ) + + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } + ) + call_function = "add_stake_limit" + else: + logging.debug( + f"Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]." + ) + call_function = "add_stake" + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f":satellite: [magenta]Safe Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " - f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " - f"price limit: [green]{price_with_tolerance}[/green], " - f"original price: [green]{base_price}[/green], " - f"with partial stake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]" + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + nonce_key="coldkeypub", + period=period, + raise_error=raise_error, ) + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + logging.debug("[green]Finalized.[/green]") + + new_block = subtensor.get_current_block() + new_balance = subtensor.get_balance( + wallet.coldkeypub.ss58_address, block=new_block + ) + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block=new_block, + ) - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_stake, } - ) - call_function = "add_stake_limit" - else: - logging.info( - f":satellite: [magenta]Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]" - ) - call_function = "add_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - sign_with="coldkey", - nonce_key="coldkeypub", - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - # If we successfully staked. - if response.success: - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: return response - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + if safe_staking and "Custom error: 8" in response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] " - f"[blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - new_block = subtensor.get_current_block() - new_balance = subtensor.get_balance( - wallet.coldkeypub.ss58_address, block=new_block - ) - new_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=new_block, - ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: {new_balance}[/green]" - ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" - ) + logging.error(f"[red]{response.message}[/red]") return response - if safe_staking and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def add_stake_multiple_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -221,165 +213,189 @@ def add_stake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. + + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `add_stake_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to either: + - the corresponding `ExtrinsicResponse` object if the staking attempt was executed, or + - `None` if the staking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the stake attempt. This allows the caller to inspect which specific operations + were attempted and which were not. """ - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + ) - assert all( - [ - isinstance(netuids, list), - isinstance(hotkey_ss58s, list), - isinstance(amounts, list), - ] - ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + if len(hotkey_ss58s) == 0: + return ExtrinsicResponse(True, "Success") - if len(hotkey_ss58s) == 0: - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." + ) - assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( - "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." - ) + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("`hotkey_ss58s` must be a list of str.") - if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): - raise TypeError("hotkey_ss58s must be a list of str") + if not all(isinstance(a, Balance) for a in amounts): + raise TypeError("Each `amount` must be an instance of Balance.") - new_amounts: Sequence[Optional[Balance]] = [ - amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) - ] + new_amounts: Sequence[Optional[Balance]] = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] - if sum(amount.tao for amount in new_amounts) == 0: - # Staking 0 tao - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + if sum(amount.tao for amount in new_amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") + + block = subtensor.get_current_block() + all_stakes = subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + old_stakes: list[Balance] = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, ) - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - block = subtensor.get_current_block() - all_stakes = subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - old_stakes: list[Balance] = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - # 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 new_amounts] - ) - old_balance = initial_balance = subtensor.get_balance( - address=wallet.coldkeypub.ss58_address, block=block - ) - if total_staking_rao == 0: - # Staking all to the first wallet. - if old_balance.rao > 1000: - old_balance -= 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) - new_amounts = [ - Balance.from_tao(amount.tao * percent_reduction) for amount in new_amounts - ] + # 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 new_amounts] + ) + old_balance = initial_balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block=block + ) - successful_stakes = 0 - response = ExtrinsicResponse(False, "", extrinsic_function=get_function_name()) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, new_amounts, old_stakes, netuids) - ): - # Check enough to stake - if amount > old_balance: - logging.error( - f":cross_mark: [red]Not enough balance[/red]: [green]{old_balance}[/green] to stake: " - f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white]" - ) - continue + if total_staking_rao == 0: + # Staking all to the first wallet. + if old_balance.rao > 1000: + old_balance -= 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) + new_amounts = [ + Balance.from_tao(amount.tao * percent_reduction) + for amount in new_amounts + ] + + successful_stakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, new_amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # Check enough to stake + if amount > old_balance: + logging.warning( + f"Not enough balance: [green]{old_balance}[/green] to stake " + f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white] " + f"with hotkey: [blue]{hotkey_ss58}[/blue] on netuid [blue]{netuid}[/blue]." + ) + continue - try: - logging.info( - f"Staking [blue]{amount}[/blue] to hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " - f"[blue]{netuid}[/blue]" - ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": hotkey_ss58, - "amount_staked": amount.rao, - "netuid": netuid, - }, - ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - nonce_key="coldkeypub", - sign_with="coldkey", - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + try: + logging.debug( + f"Staking [blue]{amount}[/blue] to hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + response = add_stake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) - # If we successfully staked. - if response.success: - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - old_balance -= amount + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + old_balance -= amount + successful_stakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_block = subtensor.get_current_block() + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block=new_block, + ) + new_balance = subtensor.get_balance( + wallet.coldkeypub.ss58_address, block=new_block + ) + logging.debug( + f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: " + f"[green]{new_stake}[/green]" + ) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + old_balance = new_balance successful_stakes += 1 continue - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - new_block = subtensor.get_current_block() - new_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=new_block, - ) - new_balance = subtensor.get_balance( - wallet.coldkeypub.ss58_address, block=new_block - ) - logging.info( - f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + logging.warning( + f"Staking amount {amount} to hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - old_balance = new_balance - successful_stakes += 1 - else: - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - continue - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Add Stake Multiple error: {format_error_message(error)}[/red]" + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" + ) + if raise_error: + raise + + if len(netuids) > successful_stakes > 0: + success = False + message = "Some stake were successful." + elif successful_stakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one stake were successful." + + if ( + new_balance := subtensor.get_balance(wallet.coldkeypub.ss58_address) + ) != old_balance: + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + data.update( + {"balance_before": initial_balance, "balance_after": new_balance} ) - if successful_stakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/start_call.py b/bittensor/core/extrinsics/start_call.py index 9c3026d07b..af4de4620c 100644 --- a/bittensor/core/extrinsics/start_call.py +++ b/bittensor/core/extrinsics/start_call.py @@ -1,8 +1,6 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -36,26 +34,26 @@ def start_call_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + start_call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="start_call", + call_params={"netuid": netuid}, ) - start_call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="start_call", - call_params={"netuid": netuid}, - ) - - response = subtensor.sign_and_send_extrinsic( - call=start_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - return response + return subtensor.sign_and_send_extrinsic( + call=start_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/sudo.py b/bittensor/core/extrinsics/sudo.py index 9606446858..0878245818 100644 --- a/bittensor/core/extrinsics/sudo.py +++ b/bittensor/core/extrinsics/sudo.py @@ -7,6 +7,7 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet from bittensor.core.subtensor import Subtensor + from bittensor.core.types import ExtrinsicResponse def sudo_set_admin_freeze_window_extrinsic( @@ -17,7 +18,7 @@ def sudo_set_admin_freeze_window_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the admin freeze window length (in blocks) at the end of a tempo. @@ -33,9 +34,7 @@ def sudo_set_admin_freeze_window_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_admin_freeze_window" call_params = {"window": window} @@ -60,7 +59,7 @@ def sudo_set_mechanism_count_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the number of subnet mechanisms. @@ -77,9 +76,7 @@ def sudo_set_mechanism_count_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_mechanism_count" call_params = {"netuid": netuid, "mechanism_count": mech_count} @@ -104,7 +101,7 @@ def sudo_set_mechanism_emission_split_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the emission split between mechanisms in a provided subnet. @@ -121,9 +118,7 @@ def sudo_set_mechanism_emission_split_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. Note: The `maybe_split` list defines the relative emission share for each subnet mechanism. diff --git a/bittensor/core/extrinsics/take.py b/bittensor/core/extrinsics/take.py index 40411f3740..325a50ee9c 100644 --- a/bittensor/core/extrinsics/take.py +++ b/bittensor/core/extrinsics/take.py @@ -1,20 +1,19 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Literal from bittensor_wallet.bittensor_wallet import Wallet from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor.core.subtensor import Subtensor -def increase_take_extrinsic( +def set_take_extrinsic( subtensor: "Subtensor", wallet: Wallet, hotkey_ss58: str, take: int, + action: Literal["increase_take", "decrease_take"], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -27,6 +26,7 @@ def increase_take_extrinsic( wallet: The wallet to sign the extrinsic. hotkey_ss58: SS58 address of the hotkey to set take for. take: The percentage of rewards that the delegate claims from nominators. + action: The call function to use to set the take. Can be either "increase_take" or "decrease_take". period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. @@ -35,89 +35,30 @@ def increase_take_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - Tuple[bool, str]: - - True and a success message if the extrinsic is successfully submitted or processed. - - False and an error message if the submission fails or the wallet cannot be unlocked. + ExtrinsicResponse: The result object of the extrinsic execution. """ - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - -def decrease_take_extrinsic( - subtensor: "Subtensor", - wallet: Wallet, - hotkey_ss58: str, - take: int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """ - Sets the delegate `take` percentage for a neuron identified by its hotkey. + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - Parameters: - subtensor: The Subtensor instance. - wallet: The wallet to sign the extrinsic. - hotkey_ss58: SS58 address of the hotkey to set take for. - take: The percentage of rewards that the delegate claims from nominators. - period: The number of blocks during which the transaction will remain valid after it's submitted. If the - transaction is not included in a block within that number of blocks, it will expire and be rejected. You can - think of it as an expiration date for the transaction. - raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. - wait_for_inclusion: Whether to wait for the inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - Tuple[bool, str]: - - True and a success message if the extrinsic is successfully submitted or processed. - - False and an error message if the submission fails or the wallet cannot be unlocked. - """ - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=action, + call_params={ + "hotkey": hotkey_ss58, + "take": take, + }, + ) + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), - ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 3769ec9f65..888f9b1ade 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -1,12 +1,11 @@ from typing import TYPE_CHECKING, Optional + +from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK from bittensor.core.types import ExtrinsicResponse -from bittensor.core.settings import NETWORK_EXPLORER_MAP from bittensor.utils import ( get_explorer_url_for_network, get_transfer_fn_params, is_valid_bittensor_address_or_public_key, - unlock_key, - get_function_name, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -47,102 +46,99 @@ def transfer_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ - # Unlock wallet coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if amount is None and not transfer_all: + return ExtrinsicResponse( + False, "If not transferring all, `amount` must be specified." + ).with_log() + + # Validate destination address. + if not is_valid_bittensor_address_or_public_key(destination): + return ExtrinsicResponse( + False, f"Invalid destination SS58 address: {destination}" + ).with_log() + + # check existential deposit and fee + logging.debug("Fetching existential and fee.") + block = subtensor.get_current_block() + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) + if not keep_alive: + # Check if the transfer should keep_alive the account + existential_deposit = Balance(0) + else: + existential_deposit = subtensor.get_existential_deposit(block=block) + + fee = subtensor.get_transfer_fee( + wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive ) - if amount is None and not transfer_all: - message = "If not transferring all, `amount` must be specified." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Validate destination address. - if not is_valid_bittensor_address_or_public_key(destination): - message = f"Invalid destination SS58 address: {destination}" - logging.error(f":cross_mark: [red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check balance. - logging.info( - f":satellite: [magenta]Checking balance and fees on chain [/magenta] [blue]{subtensor.network}[/blue]" - ) - # check existential deposit and fee - logging.debug("Fetching existential and fee") - block = subtensor.get_current_block() - account_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - if not keep_alive: - # Check if the transfer should keep_alive the account - existential_deposit = Balance(0) - else: - existential_deposit = subtensor.get_existential_deposit(block=block) - - fee = subtensor.get_transfer_fee( - wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive - ) - - # Check if we have enough balance. - if transfer_all is True: - if (account_balance - fee) < existential_deposit: - message = "Not enough balance to transfer." - logging.error(message) + # Check if we have enough balance. + if transfer_all: + if (old_balance - fee) < existential_deposit: + return ExtrinsicResponse( + False, "Not enough balance to transfer all stake." + ).with_log() + + elif old_balance < (amount + fee + existential_deposit): return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) - elif account_balance < (amount + fee + existential_deposit): - message = "Not enough balance." - logging.error(":cross_mark: [red]Not enough balance[/red]") - logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]") - logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]") - logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info(":satellite: [magenta]Transferring...[/magenta]") - - call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive) - - call = subtensor.substrate.compose_call( - call_module="Balances", - call_function=call_function, - call_params=call_params, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - block_hash = subtensor.get_block_hash() - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - logging.info(f"[green]Block Hash:[/green] [blue]{block_hash}[/blue]") - - if subtensor.network == "finney": - logging.debug("Fetching explorer URLs") - explorer_urls = get_explorer_url_for_network( - subtensor.network, block_hash, NETWORK_EXPLORER_MAP - ) - if explorer_urls: - logging.info( - f"[green]Opentensor Explorer Link: {explorer_urls.get('opentensor')}[/green]" - ) - logging.info( - f"[green]Taostats Explorer Link: {explorer_urls.get('taostats')}[/green]" - ) + False, + f"Not enough balance for transfer {amount} to {destination}. " + f"Account balance is {old_balance}. Transfers fee is {fee}.", + ).with_log() - logging.info(":satellite: [magenta]Checking Balance...[magenta]") - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + call_function, call_params = get_transfer_fn_params( + amount, destination, keep_alive ) + + call = subtensor.substrate.compose_call( + call_module="Balances", + call_function=call_function, + call_params=call_params, + ) + + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + response.transaction_fee = fee + + if response.success: + block_hash = subtensor.get_block_hash() + + if subtensor.network == DEFAULT_NETWORK: + logging.debug("Fetching explorer URLs") + explorer_urls = get_explorer_url_for_network( + subtensor.network, block_hash, NETWORK_EXPLORER_MAP + ) + if explorer_urls: + logging.debug( + f"[green]Opentensor Explorer Link: {explorer_urls.get('opentensor')}[/green]" + ) + logging.debug( + f"[green]Taostats Explorer Link: {explorer_urls.get('taostats')}[/green]" + ) + + new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + } + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index a926fdd9d8..6e2d2bdb27 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -1,12 +1,10 @@ from typing import Optional, TYPE_CHECKING -from bittensor.core.types import ExtrinsicResponse + from async_substrate_interface.errors import SubstrateRequestException -from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.utils import unlock_key, format_error_message, get_function_name -from bittensor.core.types import UIDs -from bittensor.utils import unlock_key, format_error_message +from bittensor.core.types import ExtrinsicResponse, UIDs +from bittensor.utils import format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -51,139 +49,137 @@ def unstake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - block = subtensor.get_current_block() - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - old_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=block, - ) - - # unstaking_balance = amount - amount.set_unit(netuid) - - # Check enough to unstake. - if amount > old_stake: - message = f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {wallet.hotkey_str}" - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } - - if safe_unstaking: - pool = subtensor.subnet(netuid=netuid) - base_price = pool.price.tao - - if pool.netuid == 0: - price_with_tolerance = base_price - else: - price_with_tolerance = base_price * (1 - rate_tolerance) - - logging_info = ( - f":satellite: [magenta]Safe Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " - f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " - f"price limit: [green]{price_with_tolerance}[/green], " - f"original price: [green]{base_price}[/green], " - f"with partial unstake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" - else: - logging_info = ( - f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - call_function = "remove_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - fee = get_extrinsic_fee( - subtensor=subtensor, netuid=netuid, call=call, keypair=wallet.coldkeypub - ) - logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]") - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) block = subtensor.get_current_block() - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - new_stake = subtensor.get_stake( + old_balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block=block + ) + old_stake = subtensor.get_stake( coldkey_ss58=wallet.coldkeypub.ss58_address, hotkey_ss58=hotkey_ss58, netuid=netuid, block=block, ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + + # unstaking_balance = amount + amount.set_unit(netuid) + + # Check enough to unstake. + if amount > old_stake: + return ExtrinsicResponse( + False, + f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {hotkey_ss58}", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_unstaked": amount.rao, + } + + if safe_unstaking: + pool = subtensor.subnet(netuid=netuid) + base_price = pool.price.tao + + if pool.netuid == 0: + price_with_tolerance = base_price + else: + price_with_tolerance = base_price * (1 - rate_tolerance) + + logging_message = ( + f":satellite: [magenta]Safe Unstaking from:[/magenta] " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " + f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " + f"price limit: [green]{price_with_tolerance}[/green], " + f"original price: [green]{base_price}[/green], " + f"with partial unstake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } + ) + call_function = "remove_stake_limit" + else: + logging_message = ( + f":satellite: [magenta]Unstaking from:[/magenta] " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + call_function = "remove_stake" + + logging.debug(logging_message) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, ) + + if response.success: # If we successfully unstaked. + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + new_block = subtensor.get_current_block() + new_balance = subtensor.get_balance( + wallet.coldkeypub.ss58_address, block=new_block + ) + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block=new_block, + ) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_stake, + } + return response + + if safe_unstaking and "Custom error: 8" in response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." + + logging.error(f"[red]{response.message}[/red]") return response - if safe_unstaking and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or" - " enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def unstake_all_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -196,7 +192,7 @@ def unstake_all_extrinsic( Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. @@ -210,41 +206,42 @@ def unstake_all_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "limit_price": None, + } + + if rate_tolerance: + current_price = subtensor.subnet(netuid=netuid).price + limit_price = current_price * (1 - rate_tolerance) + call_params.update({"limit_price": limit_price}) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake_full_limit", + call_params=call_params, ) - call_params = { - "hotkey": hotkey, - "netuid": netuid, - "limit_price": None, - } - - if rate_tolerance: - current_price = subtensor.subnet(netuid=netuid).price - limit_price = current_price * (1 - rate_tolerance) - call_params.update({"limit_price": limit_price}) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake_full_limit", - call_params=call_params, - ) - - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def unstake_multiple_extrinsic( @@ -253,6 +250,7 @@ def unstake_multiple_extrinsic( netuids: UIDs, hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, + rate_tolerance: Optional[float] = 0.05, unstake_all: bool = False, period: Optional[int] = None, raise_error: bool = False, @@ -268,6 +266,7 @@ def unstake_multiple_extrinsic( netuids: List of subnets unique IDs to unstake from. hotkey_ss58s: List of hotkeys to unstake from. amounts: List of amounts to unstake. If ``None``, unstake all. + rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). unstake_all: If true, unstakes all tokens. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can @@ -278,174 +277,200 @@ def unstake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - """ - # Unlock coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - # or amounts or unstake_all (no both) - if amounts and unstake_all: - raise ValueError("Cannot specify both `amounts` and `unstake_all`.") - - if amounts is not None and not all( - isinstance(amount, Balance) for amount in amounts - ): - raise TypeError("amounts must be a [list of bittensor.Balance] or None") - - if amounts is None: - amounts = [None] * len(hotkey_ss58s) - else: - # Convert to Balance - amounts = [amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids)] - if sum(amount.tao for amount in amounts) == 0: - # Staking 0 tao - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `unstake_extrinsic` or `unstake_all_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to + either: + - the corresponding `ExtrinsicResponse` object if the unstaking attempt was executed, or + - `None` if the unstaking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the unstake attempt. This allows the caller to inspect which specific operations + were attempted and which were not. + """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + # or amounts or unstake_all (no both) + if amounts and unstake_all: + raise ValueError("Cannot specify both `amounts` and `unstake_all`.") + + if amounts is not None and not all( + isinstance(amount, Balance) for amount in amounts + ): + raise TypeError("`amounts` must be a list of Balance or None.") + + if amounts is None: + amounts = [None] * len(hotkey_ss58s) + else: + # Convert to Balance + amounts = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." ) - assert all( - [ - isinstance(netuids, list), - isinstance(hotkey_ss58s, list), - isinstance(amounts, list), - ] - ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." - - if len(hotkey_ss58s) == 0: - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if len(hotkey_ss58s) == 0: + return ExtrinsicResponse(True, "Success") - assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( - "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." - ) - - if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): - raise TypeError("hotkey_ss58s must be a list of str") - - if amounts is not None and len(amounts) != len(hotkey_ss58s): - raise ValueError("amounts must be a list of the same length as hotkey_ss58s") - - if netuids is not None and len(netuids) != len(hotkey_ss58s): - raise ValueError("netuids must be a list of the same length as hotkey_ss58s") - - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - block = subtensor.get_current_block() - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - all_stakes = subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address - ) - old_stakes = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - successful_unstakes = 0 - response = ExtrinsicResponse( - False, "Failed", extrinsic_function=get_function_name() - ) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, amounts, old_stakes, netuids) - ): - # Convert to bittensor.Balance - if amount is None: - # Unstake it all. - unstaking_balance = old_stake - logging.warning( - f"Didn't receive any unstaking amount. Unstaking all existing stake: [blue]{old_stake}[/blue] " - f"from hotkey: [blue]{hotkey_ss58}[/blue]" + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." ) - else: - unstaking_balance = amount - unstaking_balance.set_unit(netuid) - # Check enough to unstake. - if unstaking_balance > old_stake: - logging.error( - f":cross_mark: [red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " - f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{wallet.hotkey_str}[/blue]." - ) - continue - - try: - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": hotkey_ss58, - "amount_unstaked": unstaking_balance.rao, - "netuid": netuid, - }, - ) - fee = get_extrinsic_fee( - subtensor=subtensor, netuid=netuid, call=call, keypair=wallet.coldkeypub - ) - logging.info( - f"Unstaking [blue]{unstaking_balance}[/blue] from hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " - f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]" + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("`hotkey_ss58s` must be a list of str.") + + if amounts is not None and len(amounts) != len(hotkey_ss58s): + raise ValueError( + "`amounts` must be a list of the same length as `hotkey_ss58s`." ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), + if netuids is not None and len(netuids) != len(hotkey_ss58s): + raise ValueError( + "`netuids` must be a list of the same length as `hotkey_ss58s`." ) - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. + block = subtensor.get_current_block() + old_balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block=block + ) + all_stakes = subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, + block=block, + ) + old_stakes = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, + ) + + successful_unstakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # Convert to bittensor.Balance + if amount is None: + # Unstake it all. + unstaking_balance = old_stake + logging.warning( + f"Didn't receive any unstaking amount. Unstaking all existing stake: [blue]{old_stake}[/blue] " + f"from hotkey: [blue]{hotkey_ss58}[/blue]" + ) + else: + unstaking_balance = amount + unstaking_balance.set_unit(netuid) + + # Check enough to unstake. + if unstaking_balance > old_stake: + logging.warning( + f"[red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " + f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{hotkey_ss58}[/blue]." + ) + continue - if not wait_for_finalization and not wait_for_inclusion: + try: + logging.debug( + f"Unstaking [blue]{amount}[/blue] from hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + if unstake_all: + response = unstake_all_extrinsic( + subtensor=subtensor, + wallet=wallet, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + rate_tolerance=rate_tolerance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + else: + response = unstake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=unstaking_balance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + successful_unstakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + ) + logging.debug( + f"Stake ({hotkey_ss58}) in subnet {netuid}: " + f"[blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]." + ) successful_unstakes += 1 continue - logging.info(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]..." - ) - new_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=block, - ) - logging.info( - f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + logging.warning( + f"Unstaking from hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - successful_unstakes += 1 - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - continue - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Multiple unstake filed with error: {format_error_message(error)}[/red]" + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" + ) + if raise_error: + raise + + if len(netuids) > successful_unstakes > 0: + success = False + message = "Some unstake were successful." + elif successful_unstakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one unstake were successful." + + if ( + new_balance := subtensor.get_balance(address=wallet.coldkeypub.ss58_address) + ) != old_balance: + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) - if raise_error: - raise error - return response + data.update({"balance_before": old_balance, "balance_after": new_balance}) - if successful_unstakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] ([blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 493dbf6fdf..7849144f8e 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -2,9 +2,8 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from scalecodec import GenericCall @@ -85,7 +84,7 @@ def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Execute a sudo call extrinsic. Parameters: @@ -105,15 +104,16 @@ def sudo_call_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return False, unlock.message + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type=sign_with + ) + ).success: + return unlocked + sudo_call = subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", @@ -138,7 +138,4 @@ def sudo_call_extrinsic( ) except Exception as error: - if raise_error: - raise error - - return False, str(error) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 94b69bea9b..934190c72d 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -6,12 +6,7 @@ from bittensor.core.settings import version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights -from bittensor.utils import ( - format_error_message, - get_function_name, - get_mechid_storage_index, - unlock_key, -) +from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -62,14 +57,15 @@ def commit_timelocked_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) current_block = subtensor.get_current_block() @@ -112,8 +108,8 @@ def commit_timelocked_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -129,16 +125,7 @@ def commit_timelocked_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def commit_weights_extrinsic( @@ -177,13 +164,13 @@ def commit_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights @@ -212,8 +199,8 @@ def commit_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -225,16 +212,7 @@ def commit_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def reveal_weights_extrinsic( @@ -274,14 +252,15 @@ def reveal_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = subtensor.substrate.compose_call( @@ -303,8 +282,8 @@ def reveal_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -316,16 +295,7 @@ def reveal_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def set_weights_extrinsic( @@ -363,15 +333,15 @@ def set_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked - # Convert, reformat and normalize. + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = subtensor.substrate.compose_call( @@ -392,26 +362,17 @@ def set_weights_extrinsic( wait_for_finalization=wait_for_finalization, period=period, use_nonce=True, - nonce_key="hotkey", - sign_with="hotkey", + nonce_key=signing_keypair, + sign_with=signing_keypair, raise_error=raise_error, ) if response.success: - logging.debug("Successfully set weights and Finalized.") + logging.debug(response.message) return response logging.error(response.message) return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) From 487c031125611d6fc3050fde70175e449f31f5be Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:15:27 -0700 Subject: [PATCH 04/23] improve ExtrinsicResponse class --- bittensor/core/types.py | 170 ++++++++++++++++++++++++++++++++++------ 1 file changed, 148 insertions(+), 22 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 4dae382736..94dc91bd20 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,9 +1,7 @@ import argparse from abc import ABC from dataclasses import dataclass -from typing import Any, TypedDict, Optional, Union - -from scalecodec.types import GenericExtrinsic +from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING import numpy as np from numpy.typing import NDArray @@ -12,12 +10,21 @@ from bittensor.core.chain_data import NeuronInfo, NeuronInfoLite from bittensor.core.config import Config from bittensor.utils import ( + Certificate, determine_chain_endpoint_and_network, + get_caller_name, + format_error_message, networking, - Certificate, + unlock_key, ) from bittensor.utils.btlogging import logging +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.utils.balance import Balance + from scalecodec.types import GenericExtrinsic + + # Type annotations for UIDs and weights. UIDs = Union[NDArray[np.int64], list[Union[int]]] Weights = Union[NDArray[np.float32], list[Union[int, float]]] @@ -292,10 +299,21 @@ class ExtrinsicResponse: Attributes: success: Indicates if the extrinsic execution was successful. message: A status or informational message returned from the execution (e.g., "Successfully registered subnet"). - error: Captures the underlying exception if the extrinsic failed, otherwise `None`. - data: Arbitrary data returned from the extrinsic, such as decoded events, or extra context. - extrinsic_function: The name of the SDK extrinsic function that was executed (e.g. "register_subnet_extrinsic"). + extrinsic_function: The SDK extrinsic or external function name that was executed (e.g., "add_stake_extrinsic"). extrinsic: The raw extrinsic object used in the call, if available. + extrinsic_fee: The fee charged by the extrinsic, if available. + transaction_fee: The fee charged by the transaction (e.g., fee for add_stake or transfer_stake), if available. + error: Captures the underlying exception if the extrinsic failed, otherwise `None`. + data: Arbitrary data returned from the extrinsic, such as decoded events, balance or another extra context. + + Instance methods: + as_dict: Returns a dictionary representation of this object. + with_log: Returns itself but with logging message. + + Class methods: + from_exception: Checks if error is raised or return ExtrinsicResponse accordingly. + unlock_wallet: Checks if keypair is unlocked and can be used for signing the extrinsic. + Example: import bittensor as bt @@ -309,9 +327,12 @@ class ExtrinsicResponse: ExtrinsicResponse: success: True message: Successfully registered subnet - error: None extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... + extrinsic_fee: τ1.0 + transaction_fee: τ1.0 + error: None + data: None success, message = response print(success, message) @@ -325,10 +346,12 @@ class ExtrinsicResponse: """ success: bool = True - message: str = None - error: Optional[Exception] = None + message: Optional[str] = None extrinsic_function: Optional[str] = None - extrinsic: Optional[GenericExtrinsic] = None + extrinsic: Optional["GenericExtrinsic"] = None + extrinsic_fee: Optional["Balance"] = None + transaction_fee: Optional["Balance"] = None + error: Optional[Exception] = None data: Optional[Any] = None def __iter__(self): @@ -336,29 +359,49 @@ def __iter__(self): yield self.message def __str__(self): - return str( - f"{self.__class__.__name__}:" - f"\n\tsuccess: {self.success}" - f"\n\tmessage: {self.message}" - f"\n\terror: {self.error}" - f"\n\textrinsic_function: {self.extrinsic_function}" - f"\n\textrinsic: {self.extrinsic}" - f"\n\tdata: {self.data}" + return ( + f"{self.__class__.__name__}:\n" + f"\tsuccess: {self.success}\n" + f"\tmessage: {self.message}\n" + f"\textrinsic_function: {self.extrinsic_function}\n" + f"\textrinsic: {self.extrinsic}\n" + f"\textrinsic_fee: {self.extrinsic_fee}\n" + f"\ttransaction_fee: {self.transaction_fee}\n" + f"\tdata: {self.data}\n" + f"\terror: {self.error}" ) def __repr__(self): return repr((self.success, self.message)) + def as_dict(self) -> dict: + """Represents this object as a dictionary.""" + return { + "success": self.success, + "message": self.message, + "extrinsic_function": self.extrinsic_function, + "extrinsic": self.extrinsic, + "extrinsic_fee": str(self.extrinsic_fee) if self.extrinsic_fee else None, + "transaction_fee": str(self.transaction_fee) + if self.transaction_fee + else None, + "error": str(self.error) if self.error else None, + "data": self.data, + } + def __eq__(self, other: Any) -> bool: - if isinstance(other, tuple): - return (self.success, self.message) == other + if isinstance(other, (tuple, list)): + return (self.success, self.message) == tuple(other) if isinstance(other, ExtrinsicResponse): return ( self.success == other.success and self.message == other.message - and self.error == other.error and self.extrinsic_function == other.extrinsic_function and self.extrinsic == other.extrinsic + and self.extrinsic_fee == other.extrinsic_fee + and self.transaction_fee == other.transaction_fee + and self.error == other.error + and self.data == other.data ) return super().__eq__(other) @@ -374,3 +417,86 @@ def __getitem__(self, index: int) -> Any: def __len__(self): return 2 + + def __post_init__(self): + if self.extrinsic_function is None: + self.extrinsic_function = get_caller_name(depth=3) + + @classmethod + def unlock_wallet( + cls, + wallet: "Wallet", + raise_error: bool = False, + unlock_type: str = "coldkey", + nonce_key: Optional[str] = None, + ) -> "ExtrinsicResponse": + """Check if keypair is unlocked and return ExtrinsicResponse accordingly. + + Parameters: + wallet: Bittensor Wallet instance. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + unlock_type: The key type, 'coldkey' or 'hotkey'. + nonce_key: Key used for generating nonce in extrinsic function. + + Returns: + Extrinsic Response is used to check if the key is unlocked. + """ + unlock = unlock_key(wallet, unlock_type=unlock_type, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + + # If extrinsic uses `unlock_type` and `nonce_key` and `nonce_key` is not public, we need to check the + # availability of both keys. + if nonce_key and nonce_key != unlock_type and "pub" not in nonce_key: + nonce_key_unlock = unlock_key( + wallet, unlock_type=nonce_key, raise_error=raise_error + ) + if not nonce_key_unlock.success: + logging.error(nonce_key_unlock.message) + + return cls( + success=all([unlock.success, nonce_key_unlock.success]), + message=unlock.message, + extrinsic_function=get_caller_name(), + ) + + return cls( + success=unlock.success, + message=unlock.message, + extrinsic_function=get_caller_name(), + ) + + @classmethod + def from_exception(cls, raise_error: bool, error: Exception) -> "ExtrinsicResponse": + """Check if error is raised and return ExtrinsicResponse accordingly. + Parameters: + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + error: Exception raised during extrinsic execution. + + Returns: + Extrinsic Response with False checks whether to raise an error or simply return the instance. + """ + if raise_error: + raise error + return cls( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_caller_name(), + ).with_log() + + def with_log( + self, level: Literal["trace", "debug", "info", "warning", "error", "success"] = "error" + ) -> "ExtrinsicResponse": + """Logs provided message with provided level. + + Parameters: + level: Logging level represented as "trace", "debug", "info", "warning", "error", "success" uses to logging + message. + + Returns: + ExtrinsicResponse instance. + """ + if self.message: + getattr(logging, level)(self.message) + return self From 0cfad5f18619601b2d57334f09c0c191ef0ee86b Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:16:37 -0700 Subject: [PATCH 05/23] update/fix tests --- Makefile | 1 - bittensor/utils/mock/subtensor_mock.py | 66 +- tests/e2e_tests/conftest.py | 50 +- tests/e2e_tests/test_axon.py | 10 - tests/e2e_tests/test_commit_reveal.py | 45 +- tests/e2e_tests/test_commit_weights.py | 14 - tests/e2e_tests/test_commitment.py | 26 +- tests/e2e_tests/test_delegate.py | 40 - tests/e2e_tests/test_dendrite.py | 200 +- tests/e2e_tests/test_hotkeys.py | 576 ++-- tests/e2e_tests/test_incentive.py | 14 +- tests/e2e_tests/test_liquid_alpha.py | 4 - tests/e2e_tests/test_liquidity.py | 8 +- tests/e2e_tests/test_metagraph.py | 16 - tests/e2e_tests/test_neuron_certificate.py | 4 - tests/e2e_tests/test_reveal_commitments.py | 20 +- tests/e2e_tests/test_root_set_weights.py | 8 - .../test_set_subnet_identity_extrinsic.py | 15 - tests/e2e_tests/test_set_weights.py | 4 - tests/e2e_tests/test_stake_fee.py | 6 - tests/e2e_tests/test_staking.py | 94 +- tests/e2e_tests/test_subnets.py | 8 - tests/e2e_tests/test_subtensor_functions.py | 44 +- tests/e2e_tests/test_transfer.py | 18 - tests/e2e_tests/utils/chain_interactions.py | 13 +- tests/e2e_tests/utils/e2e_test_utils.py | 2 +- tests/pytest.ini | 3 +- tests/unit_tests/conftest.py | 2 +- .../extrinsics/asyncex/test_children.py | 29 +- .../extrinsics/asyncex/test_liquidity.py | 7 - .../extrinsics/asyncex/test_mechanisms.py | 0 .../extrinsics/asyncex/test_registration.py | 7 +- .../extrinsics/asyncex/test_root.py | 50 +- .../extrinsics/asyncex/test_start_call.py | 1 - .../extrinsics/asyncex/test_transfer.py | 42 +- .../extrinsics/asyncex/test_unstaking.py | 59 +- tests/unit_tests/extrinsics/test_children.py | 9 +- tests/unit_tests/extrinsics/test_liquidity.py | 7 - .../extrinsics/test_registration.py | 33 +- tests/unit_tests/extrinsics/test_root.py | 17 +- tests/unit_tests/extrinsics/test_serving.py | 2 +- tests/unit_tests/extrinsics/test_staking.py | 86 +- .../unit_tests/extrinsics/test_start_call.py | 1 - tests/unit_tests/extrinsics/test_transfer.py | 49 +- tests/unit_tests/extrinsics/test_unstaking.py | 76 +- tests/unit_tests/test_async_subtensor.py | 200 +- tests/unit_tests/test_subtensor.py | 14 +- tests/unit_tests/test_subtensor_extended.py | 2862 ++++++++--------- 48 files changed, 2282 insertions(+), 2580 deletions(-) delete mode 100644 tests/unit_tests/extrinsics/asyncex/test_mechanisms.py diff --git a/Makefile b/Makefile index c4a49e8d1d..6d4d4db485 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,6 @@ ruff: @python -m ruff format bittensor check: ruff - @mypy --ignore-missing-imports bittensor/ --python-version=3.9 @mypy --ignore-missing-imports bittensor/ --python-version=3.10 @mypy --ignore-missing-imports bittensor/ --python-version=3.11 @mypy --ignore-missing-imports bittensor/ --python-version=3.12 diff --git a/bittensor/utils/mock/subtensor_mock.py b/bittensor/utils/mock/subtensor_mock.py index becbd90595..69ed181d7e 100644 --- a/bittensor/utils/mock/subtensor_mock.py +++ b/bittensor/utils/mock/subtensor_mock.py @@ -359,7 +359,7 @@ def set_difficulty(self, netuid: int, difficulty: int) -> None: subtensor_state["Difficulty"][netuid][self.block_number] = difficulty - def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: + def _register_neuron(self, netuid: int, hotkey_ss58: str, coldkey: str) -> int: subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["NetworksAdded"]: raise Exception("Subnet does not exist") @@ -370,7 +370,7 @@ def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: if subnetwork_n > 0 and any( self._get_most_recent_storage(subtensor_state["Keys"][netuid][uid]) - == hotkey + == hotkey_ss58 for uid in range(subnetwork_n) ): # already_registered @@ -390,18 +390,18 @@ def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: subnetwork_n + 1 ) - subtensor_state["Stake"][hotkey] = {} - subtensor_state["Stake"][hotkey][coldkey] = {} - subtensor_state["Stake"][hotkey][coldkey][self.block_number] = 0 + subtensor_state["Stake"][hotkey_ss58] = {} + subtensor_state["Stake"][hotkey_ss58][coldkey] = {} + subtensor_state["Stake"][hotkey_ss58][coldkey][self.block_number] = 0 - subtensor_state["Uids"][netuid][hotkey] = {} - subtensor_state["Uids"][netuid][hotkey][self.block_number] = uid + subtensor_state["Uids"][netuid][hotkey_ss58] = {} + subtensor_state["Uids"][netuid][hotkey_ss58][self.block_number] = uid subtensor_state["Keys"][netuid][uid] = {} - subtensor_state["Keys"][netuid][uid][self.block_number] = hotkey + subtensor_state["Keys"][netuid][uid][self.block_number] = hotkey_ss58 - subtensor_state["Owner"][hotkey] = {} - subtensor_state["Owner"][hotkey][self.block_number] = coldkey + subtensor_state["Owner"][hotkey_ss58] = {} + subtensor_state["Owner"][hotkey_ss58][self.block_number] = coldkey subtensor_state["Active"][netuid][uid] = {} subtensor_state["Active"][netuid][uid][self.block_number] = True @@ -444,16 +444,18 @@ def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: subtensor_state["Bonds"][netuid][uid] = {} subtensor_state["Bonds"][netuid][uid][self.block_number] = [] - subtensor_state["Axons"][netuid][hotkey] = {} - subtensor_state["Axons"][netuid][hotkey][self.block_number] = {} + subtensor_state["Axons"][netuid][hotkey_ss58] = {} + subtensor_state["Axons"][netuid][hotkey_ss58][self.block_number] = {} - subtensor_state["Prometheus"][netuid][hotkey] = {} - subtensor_state["Prometheus"][netuid][hotkey][self.block_number] = {} + subtensor_state["Prometheus"][netuid][hotkey_ss58] = {} + subtensor_state["Prometheus"][netuid][hotkey_ss58][self.block_number] = {} - if hotkey not in subtensor_state["IsNetworkMember"]: - subtensor_state["IsNetworkMember"][hotkey] = {} - subtensor_state["IsNetworkMember"][hotkey][netuid] = {} - subtensor_state["IsNetworkMember"][hotkey][netuid][self.block_number] = True + if hotkey_ss58 not in subtensor_state["IsNetworkMember"]: + subtensor_state["IsNetworkMember"][hotkey_ss58] = {} + subtensor_state["IsNetworkMember"][hotkey_ss58][netuid] = {} + subtensor_state["IsNetworkMember"][hotkey_ss58][netuid][ + self.block_number + ] = True return uid @@ -470,8 +472,8 @@ def _convert_to_balance(balance: Union["Balance", float, int]) -> "Balance": def force_register_neuron( self, netuid: int, - hotkey: str, - coldkey: str, + hotkey_ss58: str, + coldkey_ss58: str, stake: Union["Balance", float, int] = Balance(0), balance: Union["Balance", float, int] = Balance(0), ) -> int: @@ -485,16 +487,20 @@ def force_register_neuron( if netuid not in subtensor_state["NetworksAdded"]: raise Exception("Subnet does not exist") - uid = self._register_neuron(netuid=netuid, hotkey=hotkey, coldkey=coldkey) + uid = self._register_neuron( + netuid=netuid, hotkey_ss58=hotkey_ss58, coldkey=coldkey_ss58 + ) subtensor_state["TotalStake"][self.block_number] = ( self._get_most_recent_storage(subtensor_state["TotalStake"]) + stake.rao ) - subtensor_state["Stake"][hotkey][coldkey][self.block_number] = stake.rao + subtensor_state["Stake"][hotkey_ss58][coldkey_ss58][self.block_number] = ( + stake.rao + ) if balance.rao > 0: - self.force_set_balance(coldkey, balance) - self.force_set_balance(coldkey, balance) + self.force_set_balance(coldkey_ss58, balance) + self.force_set_balance(coldkey_ss58, balance) return uid @@ -786,18 +792,18 @@ def _get_most_recent_storage( return None def _get_axon_info( - self, netuid: int, hotkey: str, block: Optional[int] = None + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> AxonInfoDict: # Axons [netuid][hotkey][block_number] subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["Axons"]: return AxonInfoDict.default() - if hotkey not in subtensor_state["Axons"][netuid]: + if hotkey_ss58 not in subtensor_state["Axons"][netuid]: return AxonInfoDict.default() result = self._get_most_recent_storage( - subtensor_state["Axons"][netuid][hotkey], block + subtensor_state["Axons"][netuid][hotkey_ss58], block ) if not result: return AxonInfoDict.default() @@ -805,17 +811,17 @@ def _get_axon_info( return result def _get_prometheus_info( - self, netuid: int, hotkey: str, block: Optional[int] = None + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> PrometheusInfoDict: subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["Prometheus"]: return PrometheusInfoDict.default() - if hotkey not in subtensor_state["Prometheus"][netuid]: + if hotkey_ss58 not in subtensor_state["Prometheus"][netuid]: return PrometheusInfoDict.default() result = self._get_most_recent_storage( - subtensor_state["Prometheus"][netuid][hotkey], block + subtensor_state["Prometheus"][netuid][hotkey_ss58], block ) if not result: return PrometheusInfoDict.default() diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 1c2f8a42e3..579d7dcd6c 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -1,3 +1,4 @@ +import asyncio import os import re import shlex @@ -7,6 +8,7 @@ import sys import threading import time +from typing import Optional import pytest import pytest_asyncio @@ -26,8 +28,11 @@ CONTAINER_NAME_PREFIX = "test_local_chain_" -def wait_for_node_start(process, timestamp=None): - """Waits for node to start in the docker.""" +def wait_for_node_start(process, timestamp=None, timeout: Optional[int] = 120): + """Waits for node to start in the docker. + + The `timeout` is set to 2 mins bc sometimes in GH the chain takes time after finalizing the first block. + """ while True: line = process.stdout.readline() if not line: @@ -35,8 +40,7 @@ def wait_for_node_start(process, timestamp=None): timestamp = timestamp or int(time.time()) print(line.strip()) - # 10 min as timeout - if int(time.time()) - timestamp > 20 * 30: + if int(time.time()) - timestamp > timeout: print("Subtensor not started in time") raise TimeoutError @@ -111,12 +115,12 @@ def legacy_runner(params): text=True, ) as process: try: - wait_for_node_start(process) + wait_for_node_start(process, timeout=300) except TimeoutError: raise else: - with SubstrateInterface(url="ws://127.0.0.1:9944") as substrate: - yield substrate + yield + finally: # Terminate the process group (includes all child processes) os.killpg(os.getpgid(process.pid), signal.SIGTERM) @@ -132,6 +136,14 @@ def legacy_runner(params): def docker_runner(params): """Starts a Docker container before tests and gracefully terminates it after.""" + def kill_local_nodes(): + """Closes subtensor local running nodes.""" + try: + subprocess.run(["pkill", "-9", "-f", "node-subtensor"], check=False) + print("Killed all local 'node-subtensor' processes.") + except Exception as e: + print(f"Warning: failed to kill local node-subtensor: {e}") + def is_docker_running(): """Check if Docker is running and optionally skip pulling the image.""" try: @@ -155,7 +167,7 @@ def is_docker_running(): def try_start_docker(): """Run docker based on OS.""" try: - subprocess.run(["open", "-a", "Docker"], check=True) # macOS + subprocess.run(["open", "-g", "-a", "Docker"], check=True) # macOS except (FileNotFoundError, subprocess.CalledProcessError): try: subprocess.run(["systemctl", "start", "docker"], check=True) # Linux @@ -222,6 +234,8 @@ def stop_existing_test_containers(): print("Entire run command: ", cmds) + kill_local_nodes() + try_start_docker() stop_existing_test_containers() @@ -248,8 +262,7 @@ def stop_existing_test_containers(): if not result.stdout.strip(): raise RuntimeError("Docker container failed to start.") - with SubstrateInterface(url="ws://127.0.0.1:9944") as substrate: - yield substrate + yield finally: try: @@ -270,15 +283,6 @@ def subtensor(local_chain): return SubtensorApi(network="ws://localhost:9944", legacy_methods=False) -# @pytest.fixture -# def async_subtensor(local_chain): -# a_sub = SubtensorApi( -# network="ws://localhost:9944", legacy_methods=False, async_subtensor=True -# ) -# a_sub.initialize() -# return a_sub - - @pytest_asyncio.fixture async def async_subtensor(local_chain): async with SubtensorApi( @@ -315,3 +319,11 @@ def dave_wallet(): def eve_wallet(): keypair, wallet = setup_wallet("//Eve") return wallet + + +@pytest.fixture(autouse=True) +def log_test_start_and_end(request): + test_name = request.node.nodeid + logging.console.info(f"🏁[green]Testing[/green] [yellow]{test_name}[/yellow]") + yield + logging.console.success(f"✅ [green]Passed[/green] [yellow]{test_name}[/yellow]") diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index 61dff37431..2723a61261 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -19,9 +19,6 @@ async def test_axon(subtensor, templates, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing test_axon") - netuid = 2 # Register a subnet, netuid 2 @@ -81,8 +78,6 @@ async def test_axon(subtensor, templates, alice_wallet): "Coldkey mismatch after mining" ) - logging.console.success("✅ Passed test_axon") - @pytest.mark.asyncio async def test_axon_async(async_subtensor, templates, alice_wallet): @@ -97,9 +92,6 @@ async def test_axon_async(async_subtensor, templates, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing test_axon") - netuid = 2 # Register a subnet, netuid 2 @@ -162,5 +154,3 @@ async def test_axon_async(async_subtensor, templates, alice_wallet): assert updated_axon.coldkey == alice_wallet.coldkey.ss58_address, ( "Coldkey mismatch after mining" ) - - logging.console.success("✅ Passed test_axon_async") diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 3f460391c8..ee8d10386d 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -28,11 +28,10 @@ TESTED_SUB_SUBNETS = 2 -# @pytest.mark.parametrize("local_chain", [True], indirect=True) @pytest.mark.asyncio -async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_wallet): +async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): """ - Tests the commit/reveal weights mechanism (CR3) + Tests the commit/reveal weights mechanism (CRv4) Steps: 1. Register a subnet through Alice @@ -45,8 +44,6 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") - # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) @@ -77,7 +74,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - substrate=local_chain, + substrate=subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_commit_reveal_weights_enabled", value=True, @@ -97,7 +94,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Change the weights rate limit on the subnet status, error = sudo_set_admin_utils( - substrate=local_chain, + substrate=subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={"netuid": alice_subnet_netuid, "weights_set_rate_limit": "0"}, @@ -119,8 +116,8 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Change the tempo of the subnet assert ( sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={"netuid": alice_subnet_netuid, "tempo": TEMPO_TO_SET}, )[0] @@ -273,15 +270,11 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle f"latest_drand_round ({latest_drand_round}) is less than expected_reveal_round ({expected_reveal_round})" ) - logging.console.success("✅ Passed `test_commit_and_reveal_weights_cr4`") - @pytest.mark.asyncio -async def test_commit_and_reveal_weights_cr4_async( - async_subtensor, alice_wallet, local_chain -): +async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet): """ - Tests the commit/reveal weights mechanism (CR3) + Tests the commit/reveal weights mechanism (CRv4) Steps: 1. Register a subnet through Alice @@ -294,9 +287,6 @@ async def test_commit_and_reveal_weights_cr4_async( Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") - # turn off admin freeze window limit for testing assert await async_sudo_set_admin_freeze_window_extrinsic( async_subtensor, alice_wallet, 0 @@ -348,8 +338,8 @@ async def test_commit_and_reveal_weights_cr4_async( assert cr_version == 4, f"Commit reveal version is not 3, got {cr_version}" # Change the weights rate limit on the subnet - status, error = sudo_set_admin_utils( - substrate=local_chain, + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={"netuid": alice_subnet_netuid, "weights_set_rate_limit": "0"}, @@ -372,14 +362,13 @@ async def test_commit_and_reveal_weights_cr4_async( # Change the tempo of the subnet assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={"netuid": alice_subnet_netuid, "tempo": TEMPO_TO_SET}, - )[0] - is True - ) + ) + )[0] is True tempo = ( await async_subtensor.subnets.get_subnet_hyperparameters( @@ -427,8 +416,6 @@ async def test_commit_and_reveal_weights_cr4_async( f"[magenta]Testing subnet mechanism: {alice_subnet_netuid}.{mechid}[/magenta]" ) - # commit_block is the block when weights were committed on the chain (transaction block) - expected_commit_block = await async_subtensor.block + 1 # Commit weights response = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, @@ -531,5 +518,3 @@ async def test_commit_and_reveal_weights_cr4_async( assert latest_drand_round - expected_reveal_round >= -3, ( f"latest_drand_round ({latest_drand_round}) is less than expected_reveal_round ({expected_reveal_round})" ) - - logging.console.success("✅ Passed `test_commit_and_reveal_weights_cr4`") diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 53c8c389df..692fe29cd6 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -42,8 +42,6 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights") - # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" @@ -143,7 +141,6 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): ) assert response.success, response.message - logging.console.info(f"block: {subtensor.block} response: {response}") storage_index = get_mechid_storage_index(netuid, mechid) weight_commits = subtensor.queries.query_module( @@ -192,8 +189,6 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights[0][1]}" ) - logging.console.success("✅ Passed test_commit_and_reveal_weights") - @pytest.mark.asyncio async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wallet): @@ -209,8 +204,6 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights_async") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( @@ -352,7 +345,6 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal assert weight_vals[0] == revealed_weights[0][1], ( f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights[0][1]}" ) - logging.console.success("✅ Passed test_commit_and_reveal_weights_async") # Create different committed data to avoid coming into the pool's blacklist with the error @@ -385,8 +377,6 @@ async def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights") - # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" @@ -522,7 +512,6 @@ def send_commit(salt_, weight_uids_, weight_vals_): assert len(weight_commits.value) == AMOUNT_OF_COMMIT_WEIGHTS, ( "Expected exact list of weight commits" ) - logging.console.success("✅ Passed `test_commit_and_reveal_weights` test.") @pytest.mark.asyncio @@ -540,8 +529,6 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights") - assert ( await async_sudo_set_admin_utils( substrate=async_subtensor.substrate, @@ -722,4 +709,3 @@ async def send_commit_(): assert len(weight_commits.value) == AMOUNT_OF_COMMIT_WEIGHTS, ( "Expected exact list of weight commits" ) - logging.console.success("✅ Passed `test_commit_and_reveal_weights_async` test.") diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index 825f3a3625..a886ae5bb3 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -1,18 +1,16 @@ import pytest from async_substrate_interface.errors import SubstrateRequestException -from bittensor import logging -from tests.e2e_tests.utils.chain_interactions import ( - async_sudo_set_admin_utils, - sudo_set_admin_utils, +from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.utils import sudo_call_extrinsic +from bittensor.core.extrinsics.asyncex.utils import ( + sudo_call_extrinsic as async_sudo_call_extrinsic, ) from tests.e2e_tests.utils.e2e_test_utils import ( wait_to_start_call, async_wait_to_start_call, ) -logging.set_trace() - def test_commitment(subtensor, alice_wallet, dave_wallet): dave_subnet_netuid = 2 @@ -25,9 +23,10 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): subtensor.commitments.set_commitment( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", + raise_error=True, ) assert subtensor.subnets.burned_register( @@ -53,8 +52,8 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): data="Hello World!", ) - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, + status, error = sudo_call_extrinsic( + subtensor=subtensor, wallet=alice_wallet, call_module="Commitments", call_function="set_max_space", @@ -74,6 +73,7 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!1", + raise_error=True, ) assert "Hello World!" == subtensor.commitments.get_commitment( @@ -104,9 +104,10 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): async with async_subtensor as sub: with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): await sub.commitments.set_commitment( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", + raise_error=True, ) assert ( @@ -134,8 +135,8 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): data="Hello World!", ) - status, error = await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, + status, error = await async_sudo_call_extrinsic( + subtensor=async_subtensor, wallet=alice_wallet, call_module="Commitments", call_function="set_max_space", @@ -155,6 +156,7 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): alice_wallet, netuid=dave_subnet_netuid, data="Hello World!1", + raise_error=True, ) assert "Hello World!" == await sub.commitments.get_commitment( diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 6851fc6110..176522dea8 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -37,8 +37,6 @@ def test_identity(subtensor, alice_wallet, bob_wallet): - Check Delegate's default identity - Update Delegate's identity """ - logging.console.info("Testing [green]test_identity[/green].") - identity = subtensor.neurons.query_identity(alice_wallet.coldkeypub.ss58_address) assert identity is None @@ -89,7 +87,6 @@ def test_identity(subtensor, alice_wallet, bob_wallet): name="Alice", url="https://www.example.com", ) - logging.console.success("Test [green]test_identity[/green] passed.") @pytest.mark.asyncio @@ -99,8 +96,6 @@ async def test_identity_async(async_subtensor, alice_wallet, bob_wallet): - Check Delegate's default identity - Update Delegate's identity """ - logging.console.info("Testing [green]test_identity_async[/green].") - identity = await async_subtensor.neurons.query_identity( alice_wallet.coldkeypub.ss58_address ) @@ -159,7 +154,6 @@ async def test_identity_async(async_subtensor, alice_wallet, bob_wallet): name="Alice", url="https://www.example.com", ) - logging.console.success("Test [green]test_identity_async[/green] passed.") def test_change_take(subtensor, alice_wallet, bob_wallet): @@ -169,8 +163,6 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): - Increase and decreased Delegate's take - Try corner cases (increase/decrease beyond allowed min/max) """ - - logging.console.info("Testing [green]test_change_take[/green].") with pytest.raises(HotKeyAccountNotExists): subtensor.delegates.set_delegate_take( wallet=alice_wallet, @@ -244,8 +236,6 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == 0.14999618524452582 - logging.console.success("Test [green]test_change_take[/green] passed.") - @pytest.mark.asyncio async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): @@ -255,8 +245,6 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): - Increase and decreased Delegate's take - Try corner cases (increase/decrease beyond allowed min/max) """ - - logging.console.info("Testing [green]test_change_take_async[/green].") with pytest.raises(HotKeyAccountNotExists): await async_subtensor.delegates.set_delegate_take( alice_wallet, @@ -344,8 +332,6 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): ) assert take == 0.14999618524452582 - logging.console.success("Test [green]test_change_take_async[/green] passed.") - def test_delegates(subtensor, alice_wallet, bob_wallet): """ @@ -355,8 +341,6 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): - Check if Hotkey is a Delegate - Nominator Staking """ - logging.console.info("Testing [green]test_delegates[/green].") - assert subtensor.delegates.get_delegates() == [] assert subtensor.delegates.get_delegated(alice_wallet.coldkey.ss58_address) == [] assert ( @@ -484,7 +468,6 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid), ), ] - logging.console.success("Test [green]test_delegates[/green] passed.") @pytest.mark.asyncio @@ -496,8 +479,6 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): - Check if Hotkey is a Delegate - Nominator Staking """ - logging.console.info("Testing [green]test_delegates_async[/green].") - assert await async_subtensor.delegates.get_delegates() == [] assert ( await async_subtensor.delegates.get_delegated(alice_wallet.coldkey.ss58_address) @@ -657,7 +638,6 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid), ), ] - logging.console.success("Test [green]test_delegates_async[/green] passed.") def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -668,8 +648,6 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ - Update NominatorMinRequiredStake - Check Nominator is removed """ - logging.console.info("Testing [green]test_delegates_async[/green].") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 @@ -728,10 +706,6 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ ) assert stake == Balance(0) - logging.console.success( - "Test [green]test_nominator_min_required_stake[/green] passed." - ) - @pytest.mark.asyncio async def test_nominator_min_required_stake_async( @@ -744,10 +718,6 @@ async def test_nominator_min_required_stake_async( - Update NominatorMinRequiredStake - Check Nominator is removed """ - logging.console.info( - "Testing [green]test_nominator_min_required_stake_async[/green]." - ) - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 @@ -816,10 +786,6 @@ async def test_nominator_min_required_stake_async( ) assert stake == Balance(0) - logging.console.success( - "Test [green]test_nominator_min_required_stake_async[/green] passed." - ) - def test_get_vote_data(subtensor, alice_wallet): """ @@ -829,8 +795,6 @@ def test_get_vote_data(subtensor, alice_wallet): - Votes - Checks Proposal is updated """ - logging.console.info("Testing [green]test_get_vote_data[/green].") - assert subtensor.extrinsics.root_register(alice_wallet).success, ( "Can not register Alice in root SN." ) @@ -924,7 +888,6 @@ def test_get_vote_data(subtensor, alice_wallet): nays=[], threshold=3, ) - logging.console.success("Test [green]test_get_vote_data[/green] passed.") @pytest.mark.asyncio @@ -936,8 +899,6 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): - Votes - Checks Proposal is updated """ - logging.console.info("Testing [green]test_get_vote_data_async[/green].") - assert (await async_subtensor.extrinsics.root_register(alice_wallet)).success, ( "Can not register Alice in root SN." ) @@ -1032,4 +993,3 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): nays=[], threshold=3, ) - logging.console.success("Test [green]test_get_vote_data_async[/green] passed.") diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index 7912182941..748c1fc6f8 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -5,19 +5,23 @@ from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( - async_sudo_set_admin_utils, + # async_sudo_set_admin_utils, async_wait_epoch, - sudo_set_admin_utils, + # sudo_set_admin_utils, wait_epoch, ) +from bittensor.core.extrinsics.utils import sudo_call_extrinsic +from bittensor.core.extrinsics import sudo +from bittensor.core.extrinsics.asyncex import sudo as async_sudo +from bittensor.core.extrinsics.asyncex.utils import ( + sudo_call_extrinsic as async_sudo_call_extrinsic, +) from tests.e2e_tests.utils.e2e_test_utils import ( async_wait_to_start_call, wait_to_start_call, ) -logging.on() -logging.set_debug() - +FAST_RUNTIME_TEMPO = 100 NON_FAST_RUNTIME_TEMPO = 10 @@ -35,18 +39,38 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing `test_dendrite`.") + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) + + assert sudo.sudo_set_admin_freeze_window_extrinsic( + subtensor, alice_wallet, 0 + ).success alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created." + assert subtensor.subnets.register_subnet(alice_wallet).success, ( + "Subnet wasn't created." + ) # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully." ) + assert sudo_call_extrinsic( + subtensor=subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": alice_subnet_netuid, + "tempo": SET_TEMPO, + }, + ).success, "Unable to set tempo." + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid), ( "Subnet wasn't started." ) @@ -62,44 +86,32 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), ).success - # set tempo to 10 block for non-fast-runtime - assert sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={ - "netuid": alice_subnet_netuid, - "tempo": NON_FAST_RUNTIME_TEMPO, - }, - ) # update max_allowed_validators so only one neuron can get validator_permit - assert sudo_set_admin_utils( - substrate=subtensor.substrate, + assert sudo_call_extrinsic( + subtensor=subtensor, wallet=alice_wallet, call_function="sudo_set_max_allowed_validators", call_params={ "netuid": alice_subnet_netuid, "max_allowed_validators": 1, }, - ) + ).success, "Unable to set max_allowed_validators." # update weights_set_rate_limit for fast-blocks - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, + assert sudo_call_extrinsic( + subtensor=subtensor, wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={ "netuid": alice_subnet_netuid, "weights_set_rate_limit": 10, }, - ) - assert error is None - assert status is True + ).success, "Unable to set weights_set_rate_limit." # Register Bob to the network assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( - "Unable to register Bob as a neuron" + "Unable to register Bob as a neuron." ) metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) @@ -107,11 +119,15 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): # Assert neurons are Alice and Bob assert len(metagraph.neurons) == 2 - alice_neuron = metagraph.neurons[0] + alice_neuron = next( + n for n in metagraph.neurons if n.hotkey == alice_wallet.hotkey.ss58_address + ) assert alice_neuron.hotkey == alice_wallet.hotkey.ss58_address assert alice_neuron.coldkey == alice_wallet.coldkey.ss58_address - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert bob_neuron.hotkey == bob_wallet.hotkey.ss58_address assert bob_neuron.coldkey == bob_wallet.coldkey.ss58_address @@ -129,15 +145,20 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, - ).success + ).success, "Unable to stake to Bob." # Waiting to give the chain a chance to update its state subtensor.wait_for_block() # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) + logging.console.info( + f"block: {subtensor.block}, bob_neuron.stake.rao: {bob_neuron.stake.rao}, alpha.rao: {alpha.rao}, division: {bob_neuron.stake.rao / alpha.rao}" + ) # Assert alpha is close to stake equivalent assert 0.95 < bob_neuron.stake.rao / alpha.rao < 1.05 @@ -150,13 +171,17 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): async with templates.validator(bob_wallet, alice_subnet_netuid): await asyncio.sleep(5) # wait for 5 seconds for the Validator to process - await wait_epoch(subtensor, netuid=alice_subnet_netuid) + subtensor.wait_for_block( + subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 + ) # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) # Refresh validator neuron - updated_neuron = metagraph.neurons[1] + updated_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert len(metagraph.neurons) == 2 assert updated_neuron.active is True @@ -165,8 +190,6 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): assert updated_neuron.coldkey == bob_wallet.coldkey.ss58_address assert updated_neuron.pruning_score != 0 - logging.console.info("✅ Passed `test_dendrite`") - @pytest.mark.asyncio async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wallet): @@ -182,20 +205,42 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing `test_dendrite_async`.") + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if await async_subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) + + assert ( + await async_sudo.sudo_set_admin_freeze_window_extrinsic( + async_subtensor, alice_wallet, 0 + ) + ).success alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert await async_subtensor.subnets.register_subnet(alice_wallet), ( - "Subnet wasn't created" + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success, ( + "Subnet wasn't created." ) # Verify subnet created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" + "Subnet wasn't created successfully." ) + assert ( + await async_sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": alice_subnet_netuid, + "tempo": SET_TEMPO, + }, + ) + ).success, "Unable to set tempo." + assert await async_wait_to_start_call( async_subtensor, alice_wallet, alice_subnet_netuid ), "Subnet wasn't started." @@ -216,56 +261,52 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall wait_for_finalization=False, ) ).success - # set tempo to 10 block for non-fast-runtime - assert await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, + + # update max_allowed_validators so only one neuron can get validator_permit + assert ( + await async_sudo_call_extrinsic( + subtensor=async_subtensor, wallet=alice_wallet, - call_function="sudo_set_tempo", + call_function="sudo_set_max_allowed_validators", call_params={ "netuid": alice_subnet_netuid, - "tempo": NON_FAST_RUNTIME_TEMPO, + "max_allowed_validators": 1, }, ) - - # update max_allowed_validators so only one neuron can get validator_permit - assert await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_max_allowed_validators", - call_params={ - "netuid": alice_subnet_netuid, - "max_allowed_validators": 1, - }, - ) + ).success, "Unable to set max_allowed_validators." # update weights_set_rate_limit for fast-blocks - status, error = await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={ - "netuid": alice_subnet_netuid, - "weights_set_rate_limit": 10, - }, - ) - assert error is None - assert status is True + assert ( + await async_sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={ + "netuid": alice_subnet_netuid, + "weights_set_rate_limit": 10, + }, + ) + ).success, "Unable to set weights_set_rate_limit." # Register Bob to the network assert ( await async_subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid) - ).success, "Unable to register Bob as a neuron" + ).success, "Unable to register Bob as a neuron." metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) # Assert neurons are Alice and Bob assert len(metagraph.neurons) == 2 - alice_neuron = metagraph.neurons[0] + alice_neuron = next( + n for n in metagraph.neurons if n.hotkey == alice_wallet.hotkey.ss58_address + ) assert alice_neuron.hotkey == alice_wallet.hotkey.ss58_address assert alice_neuron.coldkey == alice_wallet.coldkey.ss58_address - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert bob_neuron.hotkey == bob_wallet.hotkey.ss58_address assert bob_neuron.coldkey == bob_wallet.coldkey.ss58_address @@ -287,14 +328,20 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall wait_for_inclusion=False, wait_for_finalization=False, ) - ).success + ).success, "Unable to stake to Bob." # Waiting to give the chain a chance to update its state await async_subtensor.wait_for_block() # Refresh metagraph metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) + + logging.console.info( + f"block: {await async_subtensor.block}, bob_neuron.stake.rao: {bob_neuron.stake.rao}, alpha.rao: {alpha.rao}, division: {bob_neuron.stake.rao / alpha.rao}" + ) # Assert alpha is close to stake equivalent assert 0.95 < bob_neuron.stake.rao / alpha.rao < 1.05 @@ -308,13 +355,20 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall async with templates.validator(bob_wallet, alice_subnet_netuid): await asyncio.sleep(5) # wait for 5 seconds for the Validator to process - await async_wait_epoch(async_subtensor, netuid=alice_subnet_netuid) + await async_subtensor.wait_for_block( + await async_subtensor.subnets.get_next_epoch_start_block( + alice_subnet_netuid + ) + + 1 + ) # Refresh metagraph metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) # Refresh validator neuron - updated_neuron = metagraph.neurons[1] + updated_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert len(metagraph.neurons) == 2 assert updated_neuron.active is True @@ -322,5 +376,3 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall assert updated_neuron.hotkey == bob_wallet.hotkey.ss58_address assert updated_neuron.coldkey == bob_wallet.coldkey.ss58_address assert updated_neuron.pruning_score != 0 - - logging.console.info("✅ Passed `test_dendrite_async`") diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 6416b5c404..f5fe16e857 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -1,3 +1,5 @@ +import asyncio + import pytest from bittensor.core.errors import ( @@ -11,14 +13,9 @@ TxRateLimitExceeded, NonAssociatedColdKey, ) -from bittensor.core.extrinsics.sudo import ( - sudo_set_admin_freeze_window_extrinsic, -) +from bittensor.core.extrinsics import sudo +from bittensor.core.extrinsics.asyncex import sudo as async_sudo from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.chain_interactions import ( - async_sudo_set_admin_utils, - sudo_set_admin_utils, -) from tests.e2e_tests.utils.e2e_test_utils import ( async_wait_to_start_call, wait_to_start_call, @@ -26,6 +23,8 @@ SET_CHILDREN_RATE_LIMIT = 30 ROOT_COOLDOWN = 50 # blocks +FAST_RUNTIME_TEMPO = 100 +NON_FAST_RUNTIME_TEMPO = 10 def test_hotkeys(subtensor, alice_wallet, dave_wallet): @@ -34,8 +33,6 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): - Check if Hotkey exists - Check if Hotkey is registered """ - logging.console.info("Testing [green]test_hotkeys[/green].") - dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(dave_wallet) assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( @@ -80,7 +77,6 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): ) is True ) - logging.console.success("✅ Test [green]test_hotkeys[/green] passed") @pytest.mark.asyncio @@ -90,8 +86,6 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): - Check if Hotkey exists - Check if Hotkey is registered """ - logging.console.info("Testing [green]test_hotkeys_async[/green].") - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 assert await async_subtensor.subnets.register_subnet(dave_wallet) assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( @@ -140,7 +134,6 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): ) is True ) - logging.console.success("✅ Test [green]test_hotkeys[/green] passed") def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -154,14 +147,18 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): - Trigger rate limit - Clear children list """ - - logging.console.info("Testing [green]test_children[/green].") - # turn off admin freeze window limit for testing - assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) + assert sudo.sudo_set_admin_freeze_window_extrinsic( + subtensor, alice_wallet, 0 + ).success + + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - set_tempo = 10 # affect to non-fast-blocks mode # Set cooldown success, message = subtensor.extrinsics.root_set_pending_childkey_cooldown( @@ -178,26 +175,20 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) # set the same tempo for both type of nodes (to avoid tests timeout) - if not subtensor.chain.is_fast_blocks(): - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": set_tempo}, - )[0] - is True - ) + assert sudo.sudo_call_extrinsic( + subtensor=subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, + ).success + assert subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tx_rate_limit", - call_params={"tx_rate_limit": 0}, - )[0] - is True - ) + assert sudo.sudo_call_extrinsic( + subtensor=subtensor, + wallet=alice_wallet, + call_function="sudo_set_tx_rate_limit", + call_params={"tx_rate_limit": 0}, + ).success with pytest.raises(RegistrationNotPermittedOnRootSubnet): subtensor.extrinsics.set_children( @@ -355,7 +346,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] # we use `*2` to ensure that the chain has time to process - subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2) + subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) success, children, error = subtensor.wallets.get_children( hotkey=alice_wallet.hotkey.ss58_address, @@ -439,8 +430,8 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) - sudo_set_admin_utils( - substrate=subtensor.substrate, + sudo.sudo_call_extrinsic( + subtensor=subtensor, wallet=alice_wallet, call_function="sudo_set_stake_threshold", call_params={ @@ -462,11 +453,11 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): raise_error=True, ) - logging.console.success(f"✅ Test [green]test_children[/green] passed") - @pytest.mark.asyncio -async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): +async def test_children_async( + async_subtensor, alice_wallet, bob_wallet, dave_wallet, subtensor +): """ Async tests: - Get default children (empty list) @@ -477,137 +468,175 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa - Trigger rate limit - Clear children list """ + async with async_subtensor as async_subtensor: + # turn off admin freeze window limit for testing + window_response, is_fast_blocks = await asyncio.gather( + async_sudo.sudo_set_admin_freeze_window_extrinsic( + async_subtensor, alice_wallet, 0 + ), + async_subtensor.chain.is_fast_blocks(), + ) - logging.console.info("Testing [green]test_children_async[/green].") + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - set_tempo = 10 # affect to non-fast-blocks mode + assert window_response.success, window_response.message - # Set cooldown - ( - success, - message, - ) = await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( - wallet=alice_wallet, cooldown=ROOT_COOLDOWN - ) - assert success is True, message - assert message == "Success" + SET_TEMPO = FAST_RUNTIME_TEMPO if is_fast_blocks else NON_FAST_RUNTIME_TEMPO - assert await async_subtensor.subnets.register_subnet(dave_wallet) - assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( - f"Subnet #{dave_subnet_netuid} does not exist." - ) + cooldown_response = ( + await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( + wallet=alice_wallet, cooldown=ROOT_COOLDOWN + ) + ) + assert cooldown_response.success, cooldown_response.message - assert ( - await async_wait_to_start_call(async_subtensor, dave_wallet, dave_subnet_netuid) - is True - ) + assert await async_subtensor.subnets.register_subnet(dave_wallet) - # set the same tempo for both type of nodes (to avoid tests timeout) - if not await async_subtensor.chain.is_fast_blocks(): - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, + subnet_exists, start_call, tempo_call = await asyncio.gather( + async_subtensor.subnets.subnet_exists(dave_subnet_netuid), + async_wait_to_start_call(async_subtensor, dave_wallet, dave_subnet_netuid), + async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, wallet=alice_wallet, call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": set_tempo}, + call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, + ), + ) + assert subnet_exists, "Subnet does not exist." + assert start_call, "Subnet did not start." + + tempo_call = await async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_tx_rate_limit", + call_params={"tx_rate_limit": 0}, + ) + + assert tempo_call.success, tempo_call.message + assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO + + with pytest.raises(RegistrationNotPermittedOnRootSubnet): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=0, + children=[], + raise_error=True, ) - )[0] is True - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, + with pytest.raises(NonAssociatedColdKey): + await async_subtensor.extrinsics.set_children( wallet=alice_wallet, - call_function="sudo_set_tx_rate_limit", - call_params={"tx_rate_limit": 0}, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=1, + children=[], + raise_error=True, ) - )[0] is True - assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == set_tempo - assert await async_subtensor.chain.tx_rate_limit(dave_subnet_netuid) == 0 - with pytest.raises(RegistrationNotPermittedOnRootSubnet): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=0, - children=[], - raise_error=True, - ) + with pytest.raises(SubnetNotExists): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=3, + children=[], + raise_error=True, + ) - with pytest.raises(NonAssociatedColdKey): - await async_subtensor.extrinsics.set_children( + # we can't do more than one registration within one block (avoid gather) + # https://docs.learnbittensor.org/errors/subtensor#toomanyregistrationsthisblock + alice_register_call = await async_subtensor.subnets.burned_register( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=1, - children=[], - raise_error=True, + netuid=dave_subnet_netuid, ) + await async_subtensor.wait_for_block() - with pytest.raises(SubnetNotExists): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=3, - children=[], - raise_error=True, + bob_register_call, (success, children, error) = await asyncio.gather( + async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ), + async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ), ) - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ) - ).success - logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") + assert alice_register_call.success, alice_register_call.message + logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ) - ).success - logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") + assert bob_register_call.success, bob_register_call.message + logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + assert error == "" + assert success is True + assert children == [] - assert error == "" - assert success is True - assert children == [] + with pytest.raises(InvalidChild): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + alice_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) - with pytest.raises(InvalidChild): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 1.0, - alice_wallet.hotkey.ss58_address, - ), - ], - raise_error=True, - ) + with pytest.raises(TooManyChildren): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 0.1, + bob_wallet.hotkey.ss58_address, + ) + for _ in range(10) + ], + raise_error=True, + ) - with pytest.raises(TooManyChildren): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 0.1, - bob_wallet.hotkey.ss58_address, - ) - for _ in range(10) - ], - raise_error=True, - ) + with pytest.raises(ProportionOverflow): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ], + raise_error=True, + ) - with pytest.raises(ProportionOverflow): - await async_subtensor.extrinsics.set_children( + with pytest.raises(DuplicateChild): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 0.5, + bob_wallet.hotkey.ss58_address, + ), + ( + 0.5, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) + + success, message = await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, @@ -616,102 +645,76 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa 1.0, bob_wallet.hotkey.ss58_address, ), - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), ], raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, ) + assert success, message + assert message == "Success" - with pytest.raises(DuplicateChild): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, + set_children_block = await async_subtensor.chain.get_current_block() + + # children not set yet (have to wait cool-down period) + success, children, error = await async_subtensor.wallets.get_children( hotkey=alice_wallet.hotkey.ss58_address, + block=set_children_block, netuid=dave_subnet_netuid, - children=[ - ( - 0.5, - bob_wallet.hotkey.ss58_address, - ), - ( - 0.5, - bob_wallet.hotkey.ss58_address, - ), - ], - raise_error=True, ) - success, message = await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 1.0, - bob_wallet.hotkey.ss58_address, - ), - ], - raise_error=True, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert success is True, message - assert message == "Success" - logging.console.info(f"[orange]success: {success}, message: {message}[/orange]") - - set_children_block = await async_subtensor.chain.get_current_block() - - # children not set yet (have to wait cool-down period) - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - block=set_children_block, - netuid=dave_subnet_netuid, - ) + assert success is True + assert children == [] + assert error == "" - assert success is True - assert children == [] - assert error == "" + # children are in pending state + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - # children are in pending state - pending, cooldown = await async_subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" - ) + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] - assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] + # we use `*2` to ensure that the chain has time to process + await async_subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) - # we use `*2` to ensure that the chain has time to process - await async_subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2) + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait + while not children: + await async_subtensor.wait_for_block(cooldown) + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + logging.console.info(f"block: {await async_subtensor.block}") - assert error == "" - assert success is True - assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + assert error == "" + assert success is True + assert children == [(1.0, bob_wallet.hotkey.ss58_address)] - parent_ = await async_subtensor.wallets.get_parents( - bob_wallet.hotkey.ss58_address, dave_subnet_netuid - ) + parent_ = await async_subtensor.wallets.get_parents( + bob_wallet.hotkey.ss58_address, dave_subnet_netuid + ) - assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] + assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] - # pending queue is empty - pending, cooldown = await async_subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) - assert pending == [] - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" - ) + # pending queue is empty + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) with pytest.raises(TxRateLimitExceeded): set_children_block = await async_subtensor.chain.get_current_block() @@ -730,67 +733,80 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa raise_error=True, ) - await async_subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + await async_subtensor.wait_for_block( + set_children_block + SET_CHILDREN_RATE_LIMIT + 1 + ) - success, message = await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[], - raise_error=True, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert success is True, message - assert message == "Success" - logging.console.info(f"[orange]success: {success}, message: {message}[/orange]") + success, message = await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[], + raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success is True, message + assert message == "Success" - set_children_block = await async_subtensor.chain.get_current_block() + set_children_block = await async_subtensor.chain.get_current_block() - pending, cooldown = await async_subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - assert pending == [] - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" - ) + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) - await async_subtensor.wait_for_block(cooldown + 1) + await async_subtensor.wait_for_block(cooldown + 1) - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) - - assert error == "" - assert success is True - assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - await async_subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait + while not children: + await async_subtensor.wait_for_block(cooldown) + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + logging.console.info(f"block: {await async_subtensor.block}") - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_stake_threshold", - call_params={ - "min_stake": 1_000_000_000_000, - }, - ) + assert error == "" + assert success is True + assert children == [(1.0, bob_wallet.hotkey.ss58_address)] - with pytest.raises(NotEnoughStakeToSetChildkeys): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 1.0, - bob_wallet.hotkey.ss58_address, - ), - ], - raise_error=True, + await async_subtensor.wait_for_block( + set_children_block + SET_CHILDREN_RATE_LIMIT ) - logging.console.success(f"✅ Test [green]test_children_async[/green] passed") + assert ( + await async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_stake_threshold", + call_params={ + "min_stake": 1_000_000_000_000, + }, + ) + ).success + + with pytest.raises(NotEnoughStakeToSetChildkeys): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 4e6e14c468..ed1c180a54 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -27,8 +27,6 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing [blue]test_incentive[/blue]") alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # turn off admin freeze window limit for testing assert ( @@ -75,7 +73,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): # Wait for the first epoch to pass subtensor.wait_for_block( - subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 + subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 5 ) # Get current miner/validator stats @@ -188,14 +186,11 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): ), ] - print("✅ Passed test_incentive") break except Exception: subtensor.wait_for_block(subtensor.block) continue - logging.console.success("Test [green]test_incentive[/green] passed.") - @pytest.mark.asyncio async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wallet): @@ -210,8 +205,6 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing [blue]test_incentive[/blue]") alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # turn off admin freeze window limit for testing @@ -264,7 +257,7 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal next_epoch_start_block = await async_subtensor.subnets.get_next_epoch_start_block( netuid=alice_subnet_netuid ) - await async_subtensor.wait_for_block(next_epoch_start_block + 1) + await async_subtensor.wait_for_block(next_epoch_start_block + 5) # Get current miner/validator stats alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[ @@ -380,10 +373,7 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal ), ] - print("✅ Passed test_incentive") break except Exception: await async_subtensor.wait_for_block(await async_subtensor.block) continue - - logging.console.success("Test [green]test_incentive[/green] passed.") diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 0dd6e0a4fb..8d5fd2b6b9 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -38,8 +38,6 @@ def test_liquid_alpha(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_liquid_alpha_enabled") - # turn off admin freeze window limit for testing assert ( sudo_set_admin_utils( @@ -244,8 +242,6 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_liquid_alpha_async[/blue]") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index d180dc3759..62e0dfe55d 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -25,8 +25,6 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): 9. Remove all stake from Alice and check `get_liquidity_list` return new liquidity positions with 0 fees_tao. 10. Remove all liquidity positions and check `get_liquidity_list` return empty list. """ - logging.console.info("Testing [blue]test_liquidity[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Make sure `get_liquidity_list` return None if SN doesn't exist @@ -258,8 +256,8 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Bob remove all stake from alice assert subtensor.extrinsics.unstake_all( wallet=bob_wallet, - hotkey=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior wait_for_inclusion=True, wait_for_finalization=True, @@ -314,8 +312,6 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): 9. Remove all stake from Alice and check `get_liquidity_list` return new liquidity positions with 0 fees_tao. 10. Remove all liquidity positions and check `get_liquidity_list` return empty list. """ - logging.console.info("Testing [blue]test_liquidity_async[/blue]") - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Make sure `get_liquidity_list` return None if SN doesn't exist @@ -558,8 +554,8 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert ( await async_subtensor.extrinsics.unstake_all( wallet=bob_wallet, - hotkey=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior wait_for_inclusion=True, wait_for_finalization=True, diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 6b9385fd51..aed0270286 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -52,8 +52,6 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_metagraph[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 logging.console.info("Register the subnet through Alice") @@ -210,8 +208,6 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_metagraph_async[/blue]") - async with async_subtensor: alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 @@ -377,8 +373,6 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ - logging.console.info("Testing [blue]test_metagraph_info[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet) @@ -633,8 +627,6 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ - logging.console.info("Testing [blue]test_metagraph_info_async[/blue]") - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 assert await async_subtensor.subnets.register_subnet(alice_wallet) @@ -896,8 +888,6 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ - logging.console.info("Testing [blue]test_metagraph_info_with_indexes[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet) @@ -1132,8 +1122,6 @@ async def test_metagraph_info_with_indexes_async( - Register Subnet - Check MetagraphInfo is updated """ - logging.console.info("Testing [blue]test_metagraph_info_with_indexes_async[/blue]") - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 assert await async_subtensor.subnets.register_subnet(alice_wallet) @@ -1370,8 +1358,6 @@ def test_blocks(subtensor): - Get block hash - Wait for block """ - logging.console.info("Testing [blue]test_blocks[/blue]") - get_current_block = subtensor.chain.get_current_block() block = subtensor.block @@ -1395,8 +1381,6 @@ async def test_blocks_async(subtensor): - Get block hash - Wait for block """ - logging.console.info("Testing [blue]test_blocks_async[/blue]") - block = subtensor.chain.get_current_block() assert block == subtensor.block diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index 880a8b8662..d4c80a286b 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -52,8 +52,6 @@ async def test_neuron_certificate(subtensor, alice_wallet): assert alice_wallet.hotkey.ss58_address in all_certs_query.keys() assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate - logging.console.success("✅ Passed [blue]test_neuron_certificate[/blue]") - @pytest.mark.asyncio async def test_neuron_certificate_async(async_subtensor, alice_wallet): @@ -107,5 +105,3 @@ async def test_neuron_certificate_async(async_subtensor, alice_wallet): ) assert alice_wallet.hotkey.ss58_address in all_certs_query.keys() assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate - - logging.console.success("✅ Passed [blue]test_neuron_certificate[/blue]") diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index 7d7807cd2c..865e7777d5 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -27,16 +27,12 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): Note: Actually we can run this tests in fast block mode. For this we need to set `BLOCK_TIME` to 0.25 and replace `False` to `True` in `pytest.mark.parametrize` decorator. """ - logging.console.info("Testing [blue]test_set_reveal_commitment[/blue]") - BLOCK_TIME, BLOCKS_UNTIL_REVEAL = ( (0.25, 10) if subtensor.chain.is_fast_blocks() else (12.0, 5) ) alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - logging.console.info("Testing Drand encrypted commitments.") - # Register subnet as Alice assert subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" @@ -83,14 +79,14 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): # Sometimes the chain doesn't update the repository right away and the commit doesn't appear in the expected # `last_drand_round`. In this case need to wait a bit. - print(f"Waiting for reveal round {target_reveal_round}") + logging.console.info(f"Waiting for reveal round {target_reveal_round}") chain_offset = 1 if subtensor.chain.is_fast_blocks() else 24 last_drand_round = -1 while last_drand_round <= target_reveal_round + chain_offset: # wait one drand period (3 sec) last_drand_round = subtensor.chain.last_drand_round() - print(f"Current last reveled drand round {last_drand_round}") + logging.console.info(f"Current last reveled drand round {last_drand_round}") time.sleep(3) actual_all = subtensor.commitments.get_all_revealed_commitments(alice_subnet_netuid) @@ -120,8 +116,6 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): assert message_alice == actual_alice_message assert message_bob == actual_bob_message - logging.console.success("✅ Passed [blue]test_set_reveal_commitment[/blue]") - @pytest.mark.asyncio async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wallet): @@ -142,16 +136,12 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa Note: Actually we can run this tests in fast block mode. For this we need to set `BLOCK_TIME` to 0.25 and replace `False` to `True` in `pytest.mark.parametrize` decorator. """ - logging.console.info("Testing [blue]test_set_reveal_commitment[/blue]") - BLOCK_TIME, BLOCKS_UNTIL_REVEAL = ( (0.25, 10) if await async_subtensor.chain.is_fast_blocks() else (12.0, 5) ) alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - logging.console.info("Testing Drand encrypted commitments.") - # Register subnet as Alice assert await async_subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" @@ -199,14 +189,14 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa # Sometimes the chain doesn't update the repository right away and the commit doesn't appear in the expected # `last_drand_round`. In this case need to wait a bit. - print(f"Waiting for reveal round {target_reveal_round}") + logging.console.info(f"Waiting for reveal round {target_reveal_round}") chain_offset = 1 if await async_subtensor.chain.is_fast_blocks() else 24 last_drand_round = -1 while last_drand_round <= target_reveal_round + chain_offset: # wait one drand period (3 sec) last_drand_round = await async_subtensor.chain.last_drand_round() - print(f"Current last reveled drand round {last_drand_round}") + logging.console.info(f"Current last reveled drand round {last_drand_round}") time.sleep(3) actual_all = await async_subtensor.commitments.get_all_revealed_commitments( @@ -241,5 +231,3 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa assert message_alice == actual_alice_message assert message_bob == actual_bob_message - - logging.console.success("✅ Passed [blue]test_set_reveal_commitment[/blue]") diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index dc906a86ef..5b402f0d5f 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -59,8 +59,6 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail. """ - - logging.console.info("Testing root register, weights, and hyperparams") netuid = subtensor.subnets.get_total_subnets() # 2 # Default immunity period and tempo set through the subtensor side @@ -152,8 +150,6 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall assert sn_one_neurons[alice_uid_sn_2].hotkey == alice_wallet.hotkey.ss58_address assert sn_one_neurons[alice_uid_sn_2].validator_permit is True - logging.console.success("✅ Passed root tests") - @pytest.mark.asyncio async def test_root_reg_hyperparams_async( @@ -179,8 +175,6 @@ async def test_root_reg_hyperparams_async( Raises: AssertionError: If any of the checks or verifications fail. """ - - logging.console.info("Testing root register, weights, and hyperparams") netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Default immunity period and tempo set through the subtensor side @@ -278,5 +272,3 @@ async def test_root_reg_hyperparams_async( ) assert sn_one_neurons[alice_uid_sn_2].hotkey == alice_wallet.hotkey.ss58_address assert sn_one_neurons[alice_uid_sn_2].validator_permit is True - - logging.console.success("✅ Passed root tests") diff --git a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py index 228cfdd744..1a35280aad 100644 --- a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py +++ b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py @@ -47,10 +47,6 @@ def test_set_subnet_identity_extrinsic_happy_pass(subtensor, alice_wallet): # Check SubnetIdentity of the subnet assert subtensor.subnets.subnet(netuid).subnet_identity == subnet_identity - logging.console.success( - "✅ Passed [blue]test_set_subnet_identity_extrinsic_happy_pass[/blue]" - ) - @pytest.mark.asyncio async def test_set_subnet_identity_extrinsic_happy_pass_async( @@ -102,9 +98,6 @@ async def test_set_subnet_identity_extrinsic_happy_pass_async( assert ( await async_subtensor.subnets.subnet(netuid) ).subnet_identity == subnet_identity - logging.console.success( - "✅ Passed [blue]test_set_subnet_identity_extrinsic_happy_pass_async[/blue]" - ) def test_set_subnet_identity_extrinsic_failed(subtensor, alice_wallet, bob_wallet): @@ -152,10 +145,6 @@ def test_set_subnet_identity_extrinsic_failed(subtensor, alice_wallet, bob_walle is False ), "Set subnet identity failed" - logging.console.success( - "✅ Passed [blue]test_set_subnet_identity_extrinsic_failed[/blue]" - ) - @pytest.mark.asyncio async def test_set_subnet_identity_extrinsic_failed_async( @@ -207,7 +196,3 @@ async def test_set_subnet_identity_extrinsic_failed_async( subnet_identity=subnet_identity, ) ).success is False, "Set subnet identity failed" - - logging.console.success( - "✅ Passed [blue]test_set_subnet_identity_extrinsic_failed[/blue]" - ) diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index f29b129198..582ff52a3c 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -40,7 +40,6 @@ def test_set_weights_uses_next_nonce(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_set_weights_uses_next_nonce[/blue]") # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic( subtensor=subtensor, @@ -56,7 +55,6 @@ def test_set_weights_uses_next_nonce(subtensor, alice_wallet): (0.25, 50) if subtensor.chain.is_fast_blocks() else (12.0, 20) ) - print("Testing test_set_weights_uses_next_nonce") subnet_tempo = 50 # Lower the network registration rate limit and cost @@ -229,8 +227,6 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_set_weights_uses_next_nonce_async[/blue]") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 11518cb037..4a7882e332 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -14,8 +14,6 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): - Removing stake - Moving stake between hotkeys/subnets/coldkeys """ - logging.console.info("Testing [blue]test_stake_fee_api[/blue]") - netuid = 2 root_netuid = 0 stake_amount = Balance.from_tao(100) # 100 TAO @@ -100,7 +98,6 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): assert stake_fee >= min_stake_fee, ( "Stake fee should be greater than the minimum stake fee" ) - logging.console.success("✅ Passed [blue]test_stake_fee_api[/blue]") @pytest.mark.asyncio @@ -115,8 +112,6 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): - Removing stake - Moving stake between hotkeys/subnets/coldkeys """ - logging.console.info("Testing [blue]test_stake_fee_api_async[/blue]") - netuid = 2 root_netuid = 0 stake_amount = Balance.from_tao(100) # 100 TAO @@ -203,4 +198,3 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): assert stake_fee >= min_stake_fee, ( "Stake fee should be greater than the minimum stake fee" ) - logging.console.success("✅ Passed [blue]test_stake_fee_api_async[/blue]") diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 53051dd44a..790e176444 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -25,8 +25,6 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): - Unstaking using `unstake` - Checks StakeInfo """ - logging.console.info("Testing [blue]test_single_operation[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register root as Alice - the subnet owner and validator @@ -188,7 +186,6 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): # all balances have been unstaked assert stake == Balance(0).set_unit(alice_subnet_netuid) - logging.console.success("✅ Test [green]test_single_operation[/green] passed") @pytest.mark.asyncio @@ -199,8 +196,6 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) - Unstaking using `unstake` - Checks StakeInfo """ - logging.console.info("Testing [blue]test_single_operation_async[/blue]") - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Register root as Alice - the subnet owner and validator @@ -357,7 +352,7 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) success, message = await async_subtensor.staking.unstake_all( wallet=alice_wallet, netuid=alice_subnet_netuid, - hotkey=bob_wallet.hotkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, period=16, ) assert success is True, message @@ -372,8 +367,6 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) # all balances have been unstaked assert stake == Balance(0).set_unit(alice_subnet_netuid) - logging.console.success("✅ Test [green]test_single_operation_async[/green] passed") - def test_batch_operations(subtensor, alice_wallet, bob_wallet): """ @@ -383,8 +376,6 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): - Checks StakeInfo - Checks Accounts Balance """ - logging.console.info("Testing [blue]test_batch_operations[/blue]") - netuids = [ 2, 3, @@ -486,14 +477,15 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): fee_tao = dynamic_info.alpha_to_tao(fee_alpha) expected_fee_paid += fee_tao - success = subtensor.staking.unstake_multiple( + response = subtensor.staking.unstake_multiple( wallet=alice_wallet, netuids=netuids, hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], amounts=[Balance.from_tao(100) for _ in netuids], + raise_error=True, ) - - assert success.success is True + logging.console.info(f">>> res {response}") + assert response.success, response.message for netuid, old_stake in zip(netuids, stakes): stake = subtensor.staking.get_stake( @@ -514,7 +506,6 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): ) == Balance.from_tao(999_999.7994) assert balances[alice_wallet.coldkey.ss58_address] > alice_balance - logging.console.success("✅ Test [green]test_batch_operations[/green] passed") @pytest.mark.asyncio @@ -526,8 +517,6 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) - Checks StakeInfo - Checks Accounts Balance """ - logging.console.info("Testing [blue]test_batch_operations_async[/blue]") - netuids = [ 2, 3, @@ -575,19 +564,18 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) alice_balance = balances[alice_wallet.coldkey.ss58_address] - assert ( - await async_subtensor.staking.add_stake_multiple( - alice_wallet, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - netuids=netuids, - amounts=[Balance.from_tao(10_000) for _ in netuids], - ) - ).success + response = await async_subtensor.staking.add_stake_multiple( + wallet=alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(10_000) for _ in netuids], + ) + assert response.success stakes = [ await async_subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=netuid, ) for netuid in netuids @@ -633,14 +621,13 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) fee_tao = dynamic_info.alpha_to_tao(fee_alpha) expected_fee_paid += fee_tao - success = await async_subtensor.staking.unstake_multiple( + response = await async_subtensor.staking.unstake_multiple( wallet=alice_wallet, netuids=netuids, hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], amounts=[Balance.from_tao(100) for _ in netuids], ) - - assert success.success is True + assert response.success, response.message for netuid, old_stake in zip(netuids, stakes): stake = await async_subtensor.staking.get_stake( @@ -661,7 +648,6 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) ) == Balance.from_tao(999_999.7994) assert balances[alice_wallet.coldkey.ss58_address] > alice_balance - logging.console.success("✅ Test [green]test_batch_operations_async[/green] passed") def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet): @@ -673,8 +659,6 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) 2. Succeeds with strict threshold (0.5%) and partial staking allowed 3. Succeeds with lenient threshold (10% and 30%) and no partial staking """ - logging.console.info("Testing [blue]test_safe_staking_scenarios[/blue]") - # turn off admin freeze window limit for testing assert ( sudo_set_admin_utils( @@ -856,7 +840,6 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) allow_partial_stake=False, ) assert response.success is True, "Unstake should succeed" - logging.console.success("✅ Test [green]test_safe_staking_scenarios[/green] passed") @pytest.mark.asyncio @@ -871,8 +854,6 @@ async def test_safe_staking_scenarios_async( 2. Succeeds with strict threshold (0.5%) and partial staking allowed 3. Succeeds with lenient threshold (10% and 30%) and no partial staking """ - logging.console.info("Testing [blue]test_safe_staking_scenarios_async[/blue]") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( @@ -1061,9 +1042,6 @@ async def test_safe_staking_scenarios_async( allow_partial_stake=False, ) assert response.success is True, "Unstake should succeed" - logging.console.success( - "✅ Test [green]test_safe_staking_scenarios_async[/green] passed" - ) def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): @@ -1074,8 +1052,6 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): 1. Fails with strict threshold (0.5%) 2. Succeeds with lenient threshold (10%) """ - logging.console.info("Testing [blue]test_safe_swap_stake_scenarios[/blue]") - # Create new subnet (netuid 2) and register Alice origin_netuid = 2 assert subtensor.subnets.register_subnet(bob_wallet).success @@ -1171,9 +1147,6 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): assert dest_stake > Balance(0).set_unit(dest_netuid), ( "Destination stake should be non-zero after successful swap" ) - logging.console.success( - "✅ Test [green]test_safe_swap_stake_scenarios[/green] passed" - ) @pytest.mark.asyncio @@ -1187,8 +1160,6 @@ async def test_safe_swap_stake_scenarios_async( 1. Fails with strict threshold (0.5%) 2. Succeeds with lenient threshold (10%) """ - logging.console.info("Testing [blue]test_safe_swap_stake_scenarios_async[/blue]") - # Create new subnet (netuid 2) and register Alice origin_netuid = 2 assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success @@ -1290,9 +1261,6 @@ async def test_safe_swap_stake_scenarios_async( assert dest_stake > Balance(0).set_unit(dest_netuid), ( "Destination stake should be non-zero after successful swap" ) - logging.console.success( - "✅ Test [green]test_safe_swap_stake_scenarios_async[/green] passed" - ) def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -1302,8 +1270,6 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - Moving stake from one hotkey-subnet pair to another - Testing `move_stake` method with `move_all_stake=True` flag. """ - logging.console.info("Testing [blue]test_move_stake[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet).success assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -1450,8 +1416,6 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert dave_stake.rao == CloseInValue(0, 0.00001) - logging.console.success("✅ Test [green]test_move_stake[/green] passed.") - @pytest.mark.asyncio async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -1461,8 +1425,6 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ - Moving stake from one hotkey-subnet pair to another - Testing `move_stake` method with `move_all_stake=True` flag. """ - logging.console.info("Testing [blue]test_move_stake_async[/blue]") - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -1626,8 +1588,6 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ assert dave_stake.rao == CloseInValue(0, 0.00001) - logging.console.success("✅ Test [green]test_move_stake_async[/green] passed.") - def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): """ @@ -1635,8 +1595,6 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - Adding stake - Transferring stake from one coldkey-subnet pair to another """ - logging.console.info("Testing [blue]test_transfer_stake[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet).success @@ -1751,7 +1709,6 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ), ] assert bob_stakes == expected_bob_stake - logging.console.success("✅ Test [green]test_transfer_stake[/green] passed") @pytest.mark.asyncio @@ -1763,8 +1720,6 @@ async def test_transfer_stake_async( - Adding stake - Transferring stake from one coldkey-subnet pair to another """ - logging.console.info("Testing [blue]test_transfer_stake_async[/blue]") - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success @@ -1889,7 +1844,6 @@ async def test_transfer_stake_async( ), ] assert bob_stakes == expected_bob_stake - logging.console.success("✅ Test [green]test_transfer_stake_async[/green] passed") # For test we set rate_tolerance=0.7 (70%) because of price is highly dynamic for fast-blocks and 2 SN to avoid ` @@ -1907,8 +1861,6 @@ def test_unstaking_with_limit( subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance ): """Test unstaking with limits goes well for all subnets with and without price limit.""" - logging.console.info("Testing [blue]test_unstaking_with_limit[/blue]") - # Register first SN alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet).success @@ -1986,8 +1938,8 @@ def test_unstaking_with_limit( ): subtensor.staking.unstake_all( wallet=bob_wallet, - hotkey=bob_stakes[0].hotkey_ss58, netuid=bob_stakes[0].netuid, + hotkey_ss58=bob_stakes[0].hotkey_ss58, rate_tolerance=rate_tolerance, ) else: @@ -1995,8 +1947,8 @@ def test_unstaking_with_limit( for si in bob_stakes: assert subtensor.staking.unstake_all( wallet=bob_wallet, - hotkey=si.hotkey_ss58, netuid=si.netuid, + hotkey_ss58=si.hotkey_ss58, rate_tolerance=rate_tolerance, ).success @@ -2020,8 +1972,6 @@ async def test_unstaking_with_limit_async( async_subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance ): """Test unstaking with limits goes well for all subnets with and without price limit.""" - logging.console.info("Testing [blue]test_unstaking_with_limit_async[/blue]") - # Register first SN alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success @@ -2116,7 +2066,7 @@ async def test_unstaking_with_limit_async( await async_subtensor.staking.unstake_all( wallet=bob_wallet, netuid=bob_stakes[0].netuid, - hotkey=bob_stakes[0].hotkey_ss58, + hotkey_ss58=bob_stakes[0].hotkey_ss58, rate_tolerance=rate_tolerance, ) else: @@ -2125,8 +2075,8 @@ async def test_unstaking_with_limit_async( assert ( await async_subtensor.staking.unstake_all( wallet=bob_wallet, + hotkey_ss58=si.hotkey_ss58, netuid=si.netuid, - hotkey=si.hotkey_ss58, rate_tolerance=rate_tolerance, ) ).success @@ -2136,7 +2086,3 @@ async def test_unstaking_with_limit_async( bob_wallet.coldkey.ss58_address ) assert len(bob_stakes) == 0 - - logging.console.success( - "✅ Test [green]test_unstaking_with_limit_async[/green] passed" - ) diff --git a/tests/e2e_tests/test_subnets.py b/tests/e2e_tests/test_subnets.py index 89028d2d06..7079073d82 100644 --- a/tests/e2e_tests/test_subnets.py +++ b/tests/e2e_tests/test_subnets.py @@ -9,8 +9,6 @@ def test_subnets(subtensor, alice_wallet): - Filtering subnets - Checks default TxRateLimit """ - logging.console.info("Testing [blue]test_subnets[/blue]") - subnets = subtensor.subnets.all_subnets() assert len(subnets) == 2 @@ -30,8 +28,6 @@ def test_subnets(subtensor, alice_wallet): tx_rate_limit = subtensor.chain.tx_rate_limit() assert tx_rate_limit == 1000 - logging.console.success("✅ Test [green]test_subnets[/green] passed") - @pytest.mark.asyncio async def test_subnets_async(async_subtensor, alice_wallet): @@ -41,8 +37,6 @@ async def test_subnets_async(async_subtensor, alice_wallet): - Filtering subnets - Checks default TxRateLimit """ - logging.console.info("Testing [blue]test_subnets_async[/blue]") - subnets = await async_subtensor.subnets.all_subnets() assert len(subnets) == 2 @@ -61,5 +55,3 @@ async def test_subnets_async(async_subtensor, alice_wallet): tx_rate_limit = await async_subtensor.chain.tx_rate_limit() assert tx_rate_limit == 1000 - - logging.console.success("✅ Test [green]test_subnets_async[/green] passed") diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index aebe05871b..654f69c035 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -52,7 +52,6 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_subtensor_extrinsics[/blue]") netuid = subtensor.subnets.get_total_subnets() # 22 # Initial balance for Alice, defined in the genesis file of localnet initial_alice_balance = Balance.from_tao(1_000_000) @@ -73,24 +72,12 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall pre_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() # Register subnet - assert subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet." - ) + response = subtensor.subnets.register_subnet(alice_wallet) + assert response.success, "Unable to register the subnet." # Subnet burn cost is increased immediately after a subnet is registered post_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() - # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": alice_wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - register_fee = get_extrinsic_fee(subtensor, call, alice_wallet.hotkey) - # Assert that the burn cost changed after registering a subnet assert Balance.from_tao(pre_subnet_creation_cost) < Balance.from_tao( post_subnet_creation_cost @@ -101,7 +88,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall alice_wallet.coldkeypub.ss58_address ) assert ( - alice_balance_post_sn + pre_subnet_creation_cost + register_fee + alice_balance_post_sn + pre_subnet_creation_cost + response.extrinsic_fee == initial_alice_balance ), "Balance is the same even after registering a subnet." @@ -234,8 +221,6 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall f"Expected owner {expected_owner}, but found {actual_owner}" ) - logging.console.success("✅ Passed [blue]test_subtensor_extrinsics[/blue]") - @pytest.mark.asyncio async def test_subtensor_extrinsics_async( @@ -253,7 +238,6 @@ async def test_subtensor_extrinsics_async( Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_subtensor_extrinsics[/blue]") netuid = await async_subtensor.subnets.get_total_subnets() # 22 # Initial balance for Alice, defined in the genesis file of localnet initial_alice_balance = Balance.from_tao(1_000_000) @@ -276,22 +260,8 @@ async def test_subtensor_extrinsics_async( pre_subnet_creation_cost = await async_subtensor.subnets.get_subnet_burn_cost() # Register subnet - assert await async_subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) - - # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` - call = await async_subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": alice_wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - register_fee = await get_extrinsic_fee_async( - async_subtensor, call, alice_wallet.hotkey - ) + response = await async_subtensor.subnets.register_subnet(alice_wallet) + assert response.success, "Unable to register the subnet." # Subnet burn cost is increased immediately after a subnet is registered post_subnet_creation_cost = await async_subtensor.subnets.get_subnet_burn_cost() @@ -306,7 +276,7 @@ async def test_subtensor_extrinsics_async( alice_wallet.coldkeypub.ss58_address ) assert ( - alice_balance_post_sn + pre_subnet_creation_cost + register_fee + alice_balance_post_sn + pre_subnet_creation_cost + response.extrinsic_fee == initial_alice_balance ), "Balance is the same even after registering a subnet." @@ -442,5 +412,3 @@ async def test_subtensor_extrinsics_async( assert actual_owner == expected_owner, ( f"Expected owner {expected_owner}, but found {actual_owner}" ) - - logging.console.success("✅ Passed [blue]test_subtensor_extrinsics[/blue]") diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index cfa3eede93..4f65a5828d 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -9,8 +9,6 @@ if typing.TYPE_CHECKING: from bittensor.core.subtensor_api import SubtensorApi -logging.set_trace() - def test_transfer(subtensor, alice_wallet): """ @@ -22,8 +20,6 @@ def test_transfer(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_transfer[/blue]") - transfer_value = Balance.from_tao(2) dest_coldkey = "5GpzQgpiAKHMWNSH3RN4GLf96GVTDct9QxYEFAY7LWcVzTbx" @@ -53,8 +49,6 @@ def test_transfer(subtensor, alice_wallet): f"Expected {balance_before - transfer_value - transfer_fee}, got {balance_after}" ) - logging.console.success("✅ Passed [blue]test_transfer[/blue]") - @pytest.mark.asyncio async def test_transfer_async(async_subtensor, alice_wallet): @@ -67,8 +61,6 @@ async def test_transfer_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_transfer[/blue]") - transfer_value = Balance.from_tao(2) dest_coldkey = "5GpzQgpiAKHMWNSH3RN4GLf96GVTDct9QxYEFAY7LWcVzTbx" @@ -104,12 +96,8 @@ async def test_transfer_async(async_subtensor, alice_wallet): f"Expected {balance_before - transfer_value - transfer_fee}, got {balance_after}" ) - logging.console.success("✅ Passed [blue]test_transfer[/blue]") - def test_transfer_all(subtensor, alice_wallet): - logging.console.info("Testing [blue]test_transfer_all[/blue]") - # create two dummy accounts we can drain dummy_account_1 = Wallet(path="/tmp/bittensor-dummy-account-1") dummy_account_2 = Wallet(path="/tmp/bittensor-dummy-account-2") @@ -153,14 +141,10 @@ def test_transfer_all(subtensor, alice_wallet): ) assert balance_after == Balance(0) - logging.console.success("✅ Test [green]test_transfer_all[/green] passed.") - @pytest.mark.asyncio async def test_transfer_all_async(async_subtensor, alice_wallet): # create two dummy accounts we can drain - logging.console.info("Testing [blue]test_transfer_async[/blue]") - dummy_account_1 = Wallet(path="/tmp/bittensor-dummy-account-3") dummy_account_2 = Wallet(path="/tmp/bittensor-dummy-account-4") dummy_account_1.create_new_coldkey(use_password=False, overwrite=True) @@ -208,5 +192,3 @@ async def test_transfer_all_async(async_subtensor, alice_wallet): dummy_account_2.coldkeypub.ss58_address ) assert balance_after == Balance(0) - - logging.console.success("✅ Test [green]test_transfer_async[/green] passed.") diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index b988d68557..43af7a1532 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -6,8 +6,9 @@ import asyncio import functools import time +from symtable import Class from typing import Union, Optional, TYPE_CHECKING - +from dataclasses import dataclass from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -216,10 +217,7 @@ async def wait_interval( current_block = subtensor.chain.get_current_block() if last_reported is None or current_block - last_reported >= reporting_interval: last_reported = current_block - print( - f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" - ) - logging.info( + logging.console.info( f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" ) @@ -254,10 +252,7 @@ async def async_wait_interval( current_block = await subtensor.chain.get_current_block() if last_reported is None or current_block - last_reported >= reporting_interval: last_reported = current_block - print( - f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" - ) - logging.info( + logging.console.info( f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" ) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 296f75fb0a..3ad0b896c9 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -209,7 +209,7 @@ async def _reader(self): if b"Starting validator loop." in line: logging.console.info("Validator started.") self.started.set() - elif b"Successfully set weights and Finalized." in line: + elif b"Success" in line: logging.console.info("Validator is setting weights.") self.set_weights.set() diff --git a/tests/pytest.ini b/tests/pytest.ini index 42fa0889f5..7da2f158de 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,3 +1,4 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_fixture_loop_scope = session \ No newline at end of file +asyncio_default_fixture_loop_scope = session +addopts = -s \ No newline at end of file diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 9fb5d19e41..bed6f28bb3 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -28,7 +28,7 @@ def mock_substrate(mocker): @pytest.fixture def subtensor(mock_substrate): - return bittensor.core.subtensor.Subtensor() + return bittensor.core.subtensor.Subtensor(_mock=True) @pytest.fixture diff --git a/tests/unit_tests/extrinsics/asyncex/test_children.py b/tests/unit_tests/extrinsics/asyncex/test_children.py index b89b4dc0ec..8ce1fe9563 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_children.py +++ b/tests/unit_tests/extrinsics/asyncex/test_children.py @@ -8,7 +8,7 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): """Test that set_children_extrinsic correctly constructs and submits the extrinsic.""" # Preps - hotkey = "fake hotkey" + hotkey_ss58 = "fake hotkey" netuid = 123 fake_children = [ ( @@ -17,8 +17,7 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): ), ] - substrate = subtensor.substrate.__aenter__.return_value - substrate.compose_call = mocker.AsyncMock() + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -29,13 +28,16 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): success, message = await children.set_children_extrinsic( subtensor=subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=fake_children, ) # Asserts - substrate.compose_call.assert_awaited_once_with( + assert success is True + assert "Success" in message + + mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", call_function="set_children", call_params={ @@ -51,18 +53,14 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): ) mocked_sign_and_send_extrinsic.assert_awaited_once_with( - call=substrate.compose_call.return_value, + call=mocked_compose_call.return_value, wallet=fake_wallet, period=None, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, - calling_function="set_children_extrinsic", ) - assert success is True - assert "Success" in message - @pytest.mark.asyncio async def test_root_set_pending_childkey_cooldown_extrinsic( @@ -72,8 +70,7 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( # Preps cooldown = 100 - substrate = subtensor.substrate.__aenter__.return_value - substrate.compose_call = mocker.AsyncMock() + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -88,15 +85,17 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( ) # Asserts - substrate.compose_call.call_count == 2 + assert mocked_compose_call.call_count == 2 mocked_sign_and_send_extrinsic.assert_awaited_once_with( - call=substrate.compose_call.return_value, + call=mocked_compose_call.return_value, wallet=fake_wallet, period=None, + nonce_key="hotkey", + sign_with="coldkey", + use_nonce=False, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, - calling_function="root_set_pending_childkey_cooldown_extrinsic", ) assert success is True assert "Success" in message diff --git a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py index 430c8b522f..92cc03ff18 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py +++ b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py @@ -44,10 +44,8 @@ async def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="add_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -90,10 +88,8 @@ async def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="modify_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -133,10 +129,8 @@ async def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="remove_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -177,6 +171,5 @@ async def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="toggle_user_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py b/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index 21f3c72338..047b653111 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -61,7 +61,6 @@ async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="register_extrinsic", ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" @@ -129,7 +128,6 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, mock wait_for_finalization=True, period=None, raise_error=False, - calling_function="register_extrinsic", ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" @@ -204,7 +202,7 @@ async def test_register_extrinsic_subnet_not_exists(subtensor, fake_wallet, mock ) assert result == ExtrinsicResponse( False, - f"Subnet #{netuid} does not exist.", + f"Subnet {netuid} does not exist.", extrinsic_function="register_extrinsic", ) @@ -301,7 +299,6 @@ async def is_stale_side_effect(*_, **__): wait_for_finalization=True, period=None, raise_error=False, - calling_function="register_extrinsic", ) assert result[0] is False assert result[1] == "No more attempts." @@ -366,7 +363,6 @@ async def test_set_subnet_identity_extrinsic_is_success(subtensor, fake_wallet, wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -435,7 +431,6 @@ async def test_set_subnet_identity_extrinsic_is_failed(subtensor, fake_wallet, m wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/asyncex/test_root.py b/tests/unit_tests/extrinsics/asyncex/test_root.py index 329d051c15..cc0b7112aa 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_root.py +++ b/tests/unit_tests/extrinsics/asyncex/test_root.py @@ -43,9 +43,9 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): fake_uid = 123 mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -83,7 +83,7 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -93,6 +93,8 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): module="SubtensorModule", storage_function="Uids", params=[0, "fake_hotkey_address"], + block_hash=None, + reuse_block_hash=False, ) assert result.success is True assert result.message == "Success" @@ -104,6 +106,11 @@ async def test_root_register_extrinsic_insufficient_balance( fake_wallet, mocker, ): + mocked_unlock_key = mocker.patch.object( + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), + ) mocker.patch.object( subtensor, "get_hyperparameter", @@ -122,6 +129,7 @@ async def test_root_register_extrinsic_insufficient_balance( wait_for_finalization=True, ) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False subtensor.get_balance.assert_called_once_with( @@ -146,9 +154,9 @@ async def test_root_register_extrinsic_unlock_failed(subtensor, fake_wallet, moc return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=False, message="Unlock failed"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=False, message="Unlocked"), ) # Call @@ -160,7 +168,7 @@ async def test_root_register_extrinsic_unlock_failed(subtensor, fake_wallet, moc ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False @@ -183,9 +191,9 @@ async def test_root_register_extrinsic_already_registered( return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -202,7 +210,7 @@ async def test_root_register_extrinsic_already_registered( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -228,9 +236,9 @@ async def test_root_register_extrinsic_transaction_failed( return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -253,7 +261,7 @@ async def test_root_register_extrinsic_transaction_failed( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -279,9 +287,9 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -309,7 +317,7 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -319,5 +327,7 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc module="SubtensorModule", storage_function="Uids", params=[0, "fake_hotkey_address"], + block_hash=None, + reuse_block_hash=False, ) assert result.success is False diff --git a/tests/unit_tests/extrinsics/asyncex/test_start_call.py b/tests/unit_tests/extrinsics/asyncex/test_start_call.py index 4f9bdddbd9..23d9bf755c 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_start_call.py +++ b/tests/unit_tests/extrinsics/asyncex/test_start_call.py @@ -37,7 +37,6 @@ async def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_finalization=False, period=None, raise_error=False, - calling_function="start_call_extrinsic", ) assert success is True diff --git a/tests/unit_tests/extrinsics/asyncex/test_transfer.py b/tests/unit_tests/extrinsics/asyncex/test_transfer.py index df0a82a56d..4fe074e8b7 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_transfer.py +++ b/tests/unit_tests/extrinsics/asyncex/test_transfer.py @@ -19,9 +19,9 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -56,7 +56,7 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert mocked_get_chain_head.call_count == 2 mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, @@ -71,7 +71,6 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is True @@ -92,9 +91,9 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -129,7 +128,7 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_chain_head.assert_called_once() mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, @@ -145,7 +144,6 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is False @@ -164,9 +162,9 @@ async def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, m return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -197,7 +195,7 @@ async def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, m # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_chain_head.assert_called_once() mocked_get_balance.assert_called_once() mocked_get_existential_deposit.assert_called_once_with( @@ -246,9 +244,9 @@ async def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocke fake_amount = Balance(15) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=False, message=""), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=False, message="Unlocked"), ) # Call @@ -264,7 +262,7 @@ async def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocke ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False @@ -284,9 +282,9 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -321,7 +319,7 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_chain_head.assert_called_once() mocked_get_existential_deposit.assert_called_once_with( diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 2124f2377a..8b0032a79e 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -56,12 +56,10 @@ async def test_unstake_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_extrinsic", ) @@ -82,7 +80,7 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): result = await unstaking.unstake_all_extrinsic( subtensor=fake_subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey, netuid=fake_netuid, ) @@ -104,17 +102,15 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_all_extrinsic", ) @pytest.mark.asyncio -async def test_unstake_multiple_extrinsic(fake_wallet, mocker): +async def test_unstake_multiple_extrinsic_some_unstake_is_happy(fake_wallet, mocker): """Verify that sync `unstake_multiple_extrinsic` method calls proper async method.""" # Preps fake_substrate = mocker.AsyncMock( @@ -122,13 +118,12 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) fake_subtensor = mocker.AsyncMock( **{ - "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": [Balance(10.0)], - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "tx_rate_limit.return_value": 0, "substrate": fake_substrate, } ) + mocked_unstake_extrinsic = mocker.patch.object( + unstaking, "unstake_extrinsic", return_value=ExtrinsicResponse(True, "") + ) mocker.patch.object( unstaking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] ) @@ -140,7 +135,7 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): wait_for_finalization = True # Call - result = await unstaking.unstake_multiple_extrinsic( + response = await unstaking.unstake_multiple_extrinsic( subtensor=fake_subtensor, wallet=fake_wallet, hotkey_ss58s=hotkey_ss58s, @@ -151,37 +146,19 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) # Asserts - assert result.success is True - assert fake_subtensor.substrate.compose_call.call_count == 1 - assert fake_subtensor.sign_and_send_extrinsic.call_count == 1 - - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, - ) - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, - ) - fake_subtensor.sign_and_send_extrinsic.assert_awaited_with( - call=fake_subtensor.substrate.compose_call.return_value, + print(">>> result", response) + print(">>> result.success", response.success) + assert response.success is False + assert response.message == "Some unstake were successful." + assert len(response.data) == 2 + mocked_unstake_extrinsic.assert_awaited_once_with( + subtensor=fake_subtensor, wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - sign_with="coldkey", - nonce_key="coldkeypub", - use_nonce=True, + amount=Balance.from_tao(1.1), + hotkey_ss58=hotkey_ss58s[0], + netuid=fake_netuids[0], period=None, raise_error=False, - calling_function="unstake_multiple_extrinsic", + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) diff --git a/tests/unit_tests/extrinsics/test_children.py b/tests/unit_tests/extrinsics/test_children.py index 1b0710abb5..ca5b53b0d8 100644 --- a/tests/unit_tests/extrinsics/test_children.py +++ b/tests/unit_tests/extrinsics/test_children.py @@ -5,7 +5,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): """Test that set_children_extrinsic correctly constructs and submits the extrinsic.""" # Preps - hotkey = "fake hotkey" + hotkey_ss58 = "fake hotkey" netuid = 123 fake_children = [ ( @@ -25,7 +25,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): success, message = children.set_children_extrinsic( subtensor=subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=fake_children, ) @@ -53,7 +53,6 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, - calling_function="set_children_extrinsic", ) assert success is True @@ -84,11 +83,13 @@ def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wa mocked_sign_and_send_extrinsic.assert_called_once_with( call=subtensor.substrate.compose_call.return_value, wallet=fake_wallet, + nonce_key="hotkey", + sign_with="coldkey", + use_nonce=False, period=None, raise_error=False, wait_for_inclusion=True, wait_for_finalization=False, - calling_function="root_set_pending_childkey_cooldown_extrinsic", ) assert success is True assert "Success" in message diff --git a/tests/unit_tests/extrinsics/test_liquidity.py b/tests/unit_tests/extrinsics/test_liquidity.py index eda7580d34..26ab1686d1 100644 --- a/tests/unit_tests/extrinsics/test_liquidity.py +++ b/tests/unit_tests/extrinsics/test_liquidity.py @@ -42,10 +42,8 @@ def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="add_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -87,10 +85,8 @@ def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="modify_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -129,10 +125,8 @@ def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="remove_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -172,6 +166,5 @@ def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="toggle_user_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index 286491bd55..d14685e1fa 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -45,34 +45,33 @@ def mock_new_wallet(mocker): @pytest.mark.parametrize( - "subnet_exists, neuron_is_null, cuda_available, expected_result, test_id, expected_message", + "subnet_exists, neuron_is_null, cuda_available, expected_result, expected_message", [ ( False, True, True, False, - "subnet-does-not-exist", - "Subnet #123 does not exist.", + "Subnet 123 does not exist.", ), - (True, False, True, True, "neuron-already-registered", "Already registered."), - (True, True, False, False, "cuda-unavailable", "CUDA not available."), + (True, False, True, True, "Already registered."), + (True, True, False, False, "CUDA not available."), ], + ids=["subnet-does-not-exist", "neuron-already-registered", "cuda-unavailable"], ) def test_register_extrinsic_without_pow( mock_subtensor, mock_wallet, + mocker, subnet_exists, neuron_is_null, cuda_available, expected_result, - test_id, - mocker, expected_message, ): # Arrange mocker.patch.object(mock_subtensor, "subnet_exists", return_value=subnet_exists) - mocker.patch.object( + fake_neuron = mocker.patch.object( mock_subtensor, "get_neuron_for_pubkey_and_subnet", return_value=mocker.MagicMock(is_null=neuron_is_null), @@ -101,9 +100,18 @@ def test_register_extrinsic_without_pow( ) # Assert - assert result == ExtrinsicResponse( - expected_result, expected_message, extrinsic_function="register_extrinsic" - ), f"Test failed for test_id: {test_id}" + data = ( + {"neuron": fake_neuron.return_value} + if fake_neuron.call_count > 0 and cuda_available + else None + ) + expected_result = ExtrinsicResponse( + expected_result, + expected_message, + extrinsic_function="register_extrinsic", + data=data, + ) + assert result == expected_result @pytest.mark.parametrize( @@ -216,7 +224,6 @@ def test_burned_register_extrinsic( "get_neuron_for_pubkey_and_subnet", return_value=mocker.MagicMock(is_null=neuron_is_null), ) - mocker.patch("bittensor.core.extrinsics.registration.get_extrinsic_fee") mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", @@ -291,7 +298,6 @@ def test_set_subnet_identity_extrinsic_is_success(mock_subtensor, mock_wallet, m wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -357,7 +363,6 @@ def test_set_subnet_identity_extrinsic_is_failed(mock_subtensor, mock_wallet, mo wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index 193be0f3e1..7d887964bf 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -21,12 +21,13 @@ def mock_wallet(mocker): @pytest.mark.parametrize( - "wait_for_inclusion, wait_for_finalization, hotkey_registered, registration_success, expected_result", + "wait_for_inclusion, wait_for_finalization, hotkey_registered, get_uid_for_hotkey_on_subnet, registration_success, expected_result", [ ( False, True, [True, None], + 0, True, True, ), # Already registered after attempt @@ -34,14 +35,16 @@ def mock_wallet(mocker): False, True, [False, 1], + 0, True, True, ), # Registration succeeds with user confirmation - (False, True, [False, None], False, False), # Registration fails + (False, True, [False, None], 0, False, False), # Registration fails ( False, True, [False, None], + None, True, False, ), # Registration succeeds but neuron not found @@ -59,14 +62,13 @@ def test_root_register_extrinsic( wait_for_inclusion, wait_for_finalization, hotkey_registered, + get_uid_for_hotkey_on_subnet, registration_success, expected_result, mocker, ): - # Arrange - mock_subtensor.is_hotkey_registered.return_value = hotkey_registered[0] - # Preps + mock_subtensor.is_hotkey_registered.return_value = hotkey_registered[0] mocked_sign_and_send_extrinsic = mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", @@ -82,6 +84,11 @@ def test_root_register_extrinsic( "get_balance", return_value=Balance(1), ) + mocked_get_uid_for_hotkey_on_subnet = mocker.patch.object( + mock_subtensor, + "get_uid_for_hotkey_on_subnet", + return_value=get_uid_for_hotkey_on_subnet, + ) # Act result = root.root_register_extrinsic( diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 0af11ae913..619ca2e565 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -299,6 +299,7 @@ def test_serve_axon_extrinsic( axon=mock_axon, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + raise_error=True, ) else: result = serving.serve_axon_extrinsic( @@ -375,5 +376,4 @@ def test_publish_metadata( wait_for_finalization=wait_for_finalization, period=None, raise_error=False, - calling_function="publish_metadata_extrinsic", ) diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 00e3b71ad1..0240fdf560 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -50,50 +50,30 @@ def test_add_stake_extrinsic(mocker): wait_for_inclusion=True, wait_for_finalization=True, nonce_key="coldkeypub", - sign_with="coldkey", use_nonce=True, period=None, raise_error=False, - calling_function="add_stake_extrinsic", ) -def test_add_stake_multiple_extrinsic(mocker): +def test_add_stake_multiple_extrinsic(subtensor, mocker, fake_wallet): """Verify that sync `add_stake_multiple_extrinsic` method calls proper async method.""" # Preps - fake_subtensor = mocker.Mock( - **{ - "get_balance.return_value": Balance(10.0), - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "substrate.query_multi.return_value": [ - ( - mocker.Mock( - **{ - "params": ["hotkey1"], - }, - ), - 0, - ), - ( - mocker.Mock( - **{ - "params": ["hotkey2"], - }, - ), - 0, - ), - ], - "substrate.query.return_value": 0, - } + mocked_get_stake_for_coldkey = mocker.patch.object( + subtensor, "get_stake_for_coldkey", return_value=[Balance(1.1), Balance(0.3)] + ) + mocked_get_balance = mocker.patch.object( + subtensor, "get_balance", return_value=Balance.from_tao(10) ) mocker.patch.object( staking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] ) - fake_wallet_ = mocker.Mock( - **{ - "coldkeypub.ss58_address": "hotkey_owner", - } + mocked_add_stake_extrinsic = mocker.patch.object( + staking, + "add_stake_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) + hotkey_ss58s = ["hotkey1", "hotkey2"] netuids = [1, 2] amounts = [Balance.from_tao(1.1), Balance.from_tao(2.2)] @@ -102,47 +82,19 @@ def test_add_stake_multiple_extrinsic(mocker): # Call result = staking.add_stake_multiple_extrinsic( - subtensor=fake_subtensor, - wallet=fake_wallet_, - hotkey_ss58s=hotkey_ss58s, + subtensor=subtensor, + wallet=fake_wallet, netuids=netuids, + hotkey_ss58s=hotkey_ss58s, amounts=amounts, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + raise_error=True, ) # Asserts - assert result.success is True - assert fake_subtensor.substrate.compose_call.call_count == 2 - assert fake_subtensor.sign_and_send_extrinsic.call_count == 2 - - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": "hotkey2", - "amount_staked": 2199999333, - "netuid": 2, - }, - ) - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": "hotkey2", - "amount_staked": 2199999333, - "netuid": 2, - }, - ) - fake_subtensor.sign_and_send_extrinsic.assert_called_with( - call=fake_subtensor.substrate.compose_call.return_value, - wallet=fake_wallet_, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=None, - raise_error=False, - wait_for_inclusion=True, - wait_for_finalization=True, - calling_function="add_stake_multiple_extrinsic", + mocked_get_stake_for_coldkey.assert_called_once_with( + coldkey_ss58=fake_wallet.coldkeypub.ss58_address, ) + assert result.success is True + assert mocked_add_stake_extrinsic.call_count == 2 diff --git a/tests/unit_tests/extrinsics/test_start_call.py b/tests/unit_tests/extrinsics/test_start_call.py index dd00bf2028..7f85675620 100644 --- a/tests/unit_tests/extrinsics/test_start_call.py +++ b/tests/unit_tests/extrinsics/test_start_call.py @@ -35,7 +35,6 @@ def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_finalization=False, period=None, raise_error=False, - calling_function="start_call_extrinsic", ) assert success is True diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index b56c3dbc90..e8bce586ed 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -17,9 +17,9 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -54,7 +54,7 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert mocked_get_chain_head.call_count == 1 mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, @@ -69,7 +69,6 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is True @@ -89,9 +88,9 @@ def test_transfer_extrinsic_call_successful_with_failed_response( return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -126,7 +125,7 @@ def test_transfer_extrinsic_call_successful_with_failed_response( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, block=subtensor.substrate.get_block_number.return_value, @@ -141,7 +140,6 @@ def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is False @@ -159,9 +157,9 @@ def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, mocker) return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -192,7 +190,7 @@ def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, mocker) # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_balance.assert_called_once() mocked_get_existential_deposit.assert_called_once_with( block=subtensor.substrate.get_block_number.return_value @@ -207,6 +205,12 @@ def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): fake_destination = "invalid_address" fake_amount = Balance(15) + mocked_unlock_key = mocker.patch.object( + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), + ) + mocked_is_valid_address = mocker.patch.object( transfer, "is_valid_bittensor_address_or_public_key", @@ -226,6 +230,7 @@ def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): ) # Asserts + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_valid_address.assert_called_once_with(fake_destination) assert result.success is False @@ -238,9 +243,9 @@ def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): fake_amount = Balance(15) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=False, message=""), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=False, message="Unlocked"), ) # Call @@ -256,7 +261,7 @@ def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False @@ -275,9 +280,9 @@ def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -312,7 +317,7 @@ def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert mocked_compose_call.call_count == 0 assert mocked_sign_and_send_extrinsic.call_count == 0 assert result.success is False diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 5ed2da82fd..6c8320ff8d 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -52,12 +52,10 @@ def test_unstake_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_extrinsic", ) @@ -77,7 +75,7 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): result = unstaking.unstake_all_extrinsic( subtensor=fake_subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey, netuid=fake_netuid, ) @@ -99,32 +97,29 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_all_extrinsic", ) -def test_unstake_multiple_extrinsic(fake_wallet, mocker): - """Verify that sync `unstake_multiple_extrinsic` method calls proper async method.""" +def test_unstake_multiple_extrinsic(subtensor, fake_wallet, mocker): + """Tests when out of 2 unstakes 1 is completed and 1 is not.""" # Preps - fake_substrate = mocker.Mock( - **{"get_payment_info.return_value": {"partial_fee": 10}} + mocked_balance = mocker.patch.object( + subtensor, "get_balance", return_value=Balance.from_tao(1.0) ) - fake_subtensor = mocker.Mock( - **{ - "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": [Balance(10.0)], - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "tx_rate_limit.return_value": 0, - "substrate": fake_substrate, - } + mocked_get_stake_for_coldkey_and_hotkey = mocker.patch.object( + subtensor, "get_stake_for_coldkey_and_hotkey", return_value=[Balance(10.0)] + ) + mocked_unstake_extrinsic = mocker.patch.object( + unstaking, "unstake_extrinsic", return_value=ExtrinsicResponse(True, "") ) mocker.patch.object( - unstaking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] + unstaking, + "get_old_stakes", + return_value=[Balance.from_tao(10), Balance.from_tao(0.3)], ) fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58s = ["hotkey1", "hotkey2"] @@ -135,7 +130,7 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): # Call result = unstaking.unstake_multiple_extrinsic( - subtensor=fake_subtensor, + subtensor=subtensor, wallet=fake_wallet, hotkey_ss58s=hotkey_ss58s, netuids=fake_netuids, @@ -145,37 +140,22 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) # Asserts - assert result.success is True - assert fake_subtensor.substrate.compose_call.call_count == 1 - assert fake_subtensor.sign_and_send_extrinsic.call_count == 1 - - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, + mocked_balance.assert_called_with( + address=fake_wallet.coldkeypub.ss58_address, ) - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, - ) - fake_subtensor.sign_and_send_extrinsic.assert_called_with( - call=fake_subtensor.substrate.compose_call.return_value, + + assert result.success is False + assert result.message == "Some unstake were successful." + assert len(result.data) == 2 + + mocked_unstake_extrinsic.assert_called_once_with( + subtensor=subtensor, wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - sign_with="coldkey", - nonce_key="coldkeypub", - use_nonce=True, + netuid=1, + hotkey_ss58="hotkey1", + amount=Balance.from_tao(1.1), period=None, raise_error=False, - calling_function="unstake_multiple_extrinsic", + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index f0da587734..5d594b023e 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -1,6 +1,6 @@ import datetime import unittest.mock as mock -import numpy as np + import pytest from async_substrate_interface.types import ScaleObj from bittensor_wallet import Wallet @@ -16,7 +16,7 @@ SelectiveMetagraphIndex, ) from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import U64_MAX +from bittensor.utils import U64_MAX, get_function_name from bittensor.utils.balance import Balance from tests.helpers.helpers import assert_submit_signed_extrinsic @@ -213,40 +213,17 @@ async def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, m mock_substrate.submit_extrinsic.return_value = mocker.AsyncMock( is_success=mocker.AsyncMock(return_value=True)(), ) - mocker.patch.object( - subtensor, - "get_balance", - return_value=Balance(1), - ) - mocker.patch.object( - subtensor, - "is_hotkey_registered", - return_value=False, + mocked_root_register_extrinsic = mocker.patch.object( + async_subtensor, + "root_register_extrinsic", ) - success, _ = await subtensor.burned_register( - fake_wallet, + response = await subtensor.burned_register( + wallet=fake_wallet, netuid=0, ) - assert success is True - - subtensor.is_hotkey_registered.assert_called_once_with( - netuid=0, - hotkey_ss58=fake_wallet.hotkey.ss58_address, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="root_register", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) + assert response == mocked_root_register_extrinsic.return_value @pytest.mark.asyncio @@ -595,8 +572,8 @@ async def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): mocked_query_runtime_api.assert_has_calls( [ mock.call( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=["hotkey", "coldkey", netuid], block_hash=block_hash, ) @@ -1705,6 +1682,8 @@ async def test_sign_and_send_extrinsic_success_finalization( fake_extrinsic = mocker.Mock() fake_response = mocker.Mock() + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1727,6 +1706,9 @@ async def fake_is_success(): ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey ) @@ -1736,6 +1718,11 @@ async def fake_is_success(): wait_for_finalization=True, ) assert result == (True, "Success") + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error is None + assert result.data is None @pytest.mark.asyncio @@ -1747,6 +1734,9 @@ async def test_sign_and_send_extrinsic_error_finalization( fake_call = mocker.Mock() fake_extrinsic = mocker.Mock() fake_response = mocker.Mock() + fake_error = {"some error": "message"} + + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1762,7 +1752,7 @@ async def fake_is_success(): fake_response.is_success = fake_is_success() async def fake_error_message(): - return {"some error": "message"} + return fake_error fake_response.error_message = fake_error_message() @@ -1778,6 +1768,9 @@ async def fake_error_message(): ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey ) @@ -1787,6 +1780,11 @@ async def fake_error_message(): wait_for_finalization=True, ) assert result == (False, mocked_format_error_message.return_value) + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error is fake_error + assert result.data is None @pytest.mark.asyncio @@ -1798,6 +1796,8 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( fake_call = mocker.Mock() fake_extrinsic = mocker.Mock() + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1813,6 +1813,9 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) mocked_create_signed_extrinsic.assert_awaited_once() mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey @@ -1824,6 +1827,11 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( wait_for_finalization=False, ) assert result == (True, "Not waiting for finalization or inclusion.") + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error is None + assert result.data is None @pytest.mark.asyncio @@ -1836,6 +1844,8 @@ async def test_sign_and_send_extrinsic_substrate_request_exception( fake_extrinsic = mocker.Mock() fake_exception = async_subtensor.SubstrateRequestException("Test Exception") + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1857,13 +1867,25 @@ async def test_sign_and_send_extrinsic_substrate_request_exception( ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) assert result == (False, str(fake_exception)) + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error == fake_exception + assert result.data is None @pytest.mark.asyncio async def test_sign_and_send_extrinsic_raises_error( mock_substrate, subtensor, fake_wallet, mocker ): + """Tests sign_and_send_extrinsic when an error is raised.""" + # Preps + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mock_substrate.submit_extrinsic.return_value = mocker.AsyncMock( error_message=mocker.AsyncMock( return_value={ @@ -1873,6 +1895,7 @@ async def test_sign_and_send_extrinsic_raises_error( is_success=mocker.AsyncMock(return_value=False)(), ) + # Call and asserts with pytest.raises( async_subtensor.SubstrateRequestException, match="{'name': 'Exception'}", @@ -1882,6 +1905,7 @@ async def test_sign_and_send_extrinsic_raises_error( wallet=fake_wallet, raise_error=True, ) + mocked_get_extrinsic_fee.assert_awaited_once() @pytest.mark.asyncio @@ -1911,7 +1935,7 @@ async def test_get_children_success(subtensor, mocker): ] # Call - result = await subtensor.get_children(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_children(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -1939,7 +1963,7 @@ async def test_get_children_no_children(subtensor, mocker): subtensor.substrate.query = mocked_query # Call - result = await subtensor.get_children(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_children(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -1969,7 +1993,7 @@ async def test_get_children_substrate_request_exception(subtensor, mocker): ) # Call - result = await subtensor.get_children(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_children(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -2010,7 +2034,7 @@ async def test_get_parents_success(subtensor, mocker): ] # Call - result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -2038,7 +2062,7 @@ async def test_get_parents_no_parents(subtensor, mocker): subtensor.substrate.query = mocked_query # Call - result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -2064,7 +2088,7 @@ async def test_get_parents_substrate_request_exception(subtensor, mocker): # Call with pytest.raises(async_subtensor.SubstrateRequestException): - await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + await subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) @pytest.mark.asyncio @@ -2662,8 +2686,8 @@ async def test_set_children(subtensor, fake_wallet, mocker): # Call result = await subtensor.set_children( - fake_wallet, - fake_wallet.hotkey.ss58_address, + wallet=fake_wallet, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, children=fake_children, ) @@ -2672,7 +2696,7 @@ async def test_set_children(subtensor, fake_wallet, mocker): mocked_set_children_extrinsic.assert_awaited_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, children=fake_children, wait_for_finalization=True, @@ -2698,67 +2722,53 @@ async def test_set_delegate_take_equal(subtensor, fake_wallet, mocker): subtensor.substrate.submit_extrinsic.assert_not_called() +@pytest.mark.parametrize( + "take, delegate_take, extrinsic_call", + [ + (0.1, 0.1, None), + (0.2, 0.1, "increase"), + (0.1, 0.2, "decrease"), + ], + ids=[ + "already set", + "increase_take_extrinsic", + "decrease_take_extrinsic", + ], +) @pytest.mark.asyncio async def test_set_delegate_take_increase( - mock_substrate, subtensor, fake_wallet, mocker + subtensor, fake_wallet, mocker, take, delegate_take, extrinsic_call ): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - is_success=mocker.AsyncMock(return_value=True)(), + mocked_get_delegate_take = mocker.patch.object( + subtensor, "get_delegate_take", return_value=delegate_take ) - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert ( - await subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.2, - ) - ).success - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 13107, - }, - wait_for_inclusion=True, - wait_for_finalization=True, + mocked_set_take_extrinsic = mocker.patch.object( + async_subtensor, "set_take_extrinsic" ) - - -@pytest.mark.asyncio -async def test_set_delegate_take_decrease( - mock_substrate, subtensor, fake_wallet, mocker -): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - is_success=mocker.AsyncMock(return_value=True)(), + already_set_result = ExtrinsicResponse( + True, + f"The take for {fake_wallet.hotkey.ss58_address} is already set to 0.1.", + extrinsic_function="set_delegate_take", ) - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - assert ( - await subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.1, - ) - ).success + expected_result = already_set_result + if extrinsic_call == "increase": + expected_result = mocked_set_take_extrinsic.return_value + elif extrinsic_call == "decrease": + expected_result = mocked_set_take_extrinsic.return_value - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 6553, - }, - wait_for_inclusion=True, - wait_for_finalization=True, + # Call + result = await subtensor.set_delegate_take( + wallet=fake_wallet, + hotkey_ss58=fake_wallet.hotkey.ss58_address, + take=take, ) + # Assert + mocked_get_delegate_take.assert_awaited_once_with(fake_wallet.hotkey.ss58_address) + assert result == expected_result + @pytest.mark.asyncio async def test_get_all_subnets_info_success(mocker, subtensor): @@ -3268,14 +3278,14 @@ async def test_unstake_all(subtensor, fake_wallet, mocker): # Call result = await subtensor.unstake_all( wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, ) # Asserts fake_unstake_all_extrinsic.assert_awaited_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, rate_tolerance=0.005, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index d19b2751f0..785c4bdda7 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2100,8 +2100,8 @@ def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): mocked_query_runtime_api.assert_has_calls( [ mock.call( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=["hotkey", "coldkey", netuid], block=None, ) @@ -3497,7 +3497,7 @@ def test_get_parents_success(subtensor, mocker): ] # Call - result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -3523,7 +3523,7 @@ def test_get_parents_no_parents(subtensor, mocker): subtensor.substrate.query = mocked_query # Call - result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -3561,7 +3561,7 @@ def test_set_children(subtensor, fake_wallet, mocker): mocked_set_children_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, children=fake_children, wait_for_finalization=True, @@ -3582,14 +3582,14 @@ def test_unstake_all(subtensor, fake_wallet, mocker): # Call result = subtensor.unstake_all( wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, ) # Asserts fake_unstake_all_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, rate_tolerance=0.005, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 7e90488d63..29133717de 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1,1446 +1,1416 @@ -import unittest.mock - -import async_substrate_interface.errors -import pytest - -from bittensor.core.chain_data.axon_info import AxonInfo -from bittensor.core.chain_data.chain_identity import ChainIdentity -from bittensor.core.chain_data.delegate_info import DelegatedInfo, DelegateInfo -from bittensor.core.chain_data.dynamic_info import DynamicInfo -from bittensor.core.chain_data.neuron_info import NeuronInfo -from bittensor.core.chain_data.neuron_info_lite import NeuronInfoLite -from bittensor.core.chain_data.prometheus_info import PrometheusInfo -from bittensor.core.chain_data.stake_info import StakeInfo -from bittensor.utils import U16_MAX, U64_MAX -from bittensor.utils.balance import Balance -from tests.helpers.helpers import assert_submit_signed_extrinsic - - -@pytest.fixture -def mock_delegate_info(): - return { - "delegate_ss58": tuple(bytearray(32)), - "total_stake": {}, - "nominators": [], - "owner_ss58": tuple(bytearray(32)), - "take": U16_MAX, - "validator_permits": [], - "registrations": [], - "return_per_1000": 2, - "total_daily_return": 3, - } - - -@pytest.fixture -def mock_dynamic_info(): - return { - "netuid": 0, - "owner_hotkey": tuple(bytearray(32)), - "owner_coldkey": tuple(bytearray(32)), - "subnet_name": (114, 111, 111, 116), - "token_symbol": (206, 164), - "tempo": 100, - "last_step": 4919910, - "blocks_since_last_step": 84234, - "emission": 0, - "alpha_in": 14723086336554, - "alpha_out": 6035890271491007, - "tao_in": 6035892206947246, - "alpha_out_emission": 0, - "alpha_in_emission": 0, - "tao_in_emission": 0, - "pending_alpha_emission": 0, - "pending_root_emission": 0, - "subnet_volume": 2240411565906691, - "network_registered_at": 0, - "subnet_identity": None, - "moving_price": {"bits": 0}, - } - - -@pytest.fixture -def mock_neuron_info(): - return { - "active": 0, - "axon_info": { - "ip_type": 4, - "ip": 2130706433, - "placeholder1": 0, - "placeholder2": 0, - "port": 8080, - "protocol": 0, - "version": 1, - }, - "bonds": [], - "coldkey": tuple(bytearray(32)), - "consensus": 0.0, - "dividends": 0.0, - "emission": 0.0, - "hotkey": tuple(bytearray(32)), - "incentive": 0.0, - "is_null": False, - "last_update": 0, - "netuid": 1, - "prometheus_info": { - "block": 0, - "ip_type": 0, - "ip": 0, - "port": 0, - "version": 1, - }, - "pruning_score": 0.0, - "rank": 0.0, - "stake_dict": {}, - "stake": [], - "total_stake": 1e12, - "trust": 0.0, - "uid": 1, - "validator_permit": True, - "validator_trust": 0.0, - "weights": [], - } - - -def test_all_subnets(mock_substrate, subtensor, mock_dynamic_info): - mock_substrate.runtime_call.return_value.decode.return_value = [ - mock_dynamic_info, - ] - - result = subtensor.all_subnets() - - assert result == [ - DynamicInfo( - netuid=0, - owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - subnet_name="root", - symbol="Τ", - tempo=100, - last_step=4919910, - blocks_since_last_step=84234, - emission=Balance(0), - alpha_in=Balance(14723086336554), - alpha_out=Balance(6035890271491007), - tao_in=Balance(6035892206947246), - price=Balance.from_tao(1), - k=88866962081017766138079430284, - is_dynamic=False, - alpha_out_emission=Balance(0), - alpha_in_emission=Balance(0), - tao_in_emission=Balance(0), - pending_alpha_emission=Balance(0), - pending_root_emission=Balance(0), - network_registered_at=0, - subnet_volume=Balance(2240411565906691), - subnet_identity=None, - moving_price=0.0, - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "SubnetInfoRuntimeApi", - "get_all_dynamic_info", - block_hash=None, - ) - - -def test_bonds(mock_substrate, subtensor, mocker): - mock_substrate.query_map.return_value = [ - (0, mocker.Mock(value=[(1, 100), (2, 200)])), - (1, mocker.Mock(value=[(0, 150), (2, 250)])), - (2, mocker.Mock(value=None)), - ] - - result = subtensor.bonds(netuid=1) - - assert result == [ - (0, [(1, 100), (2, 200)]), - (1, [(0, 150), (2, 250)]), - ] - - mock_substrate.query_map.assert_called_once_with( - module="SubtensorModule", - storage_function="Bonds", - params=[1], - block_hash=None, - ) - - -def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): - mock_substrate.get_payment_info.return_value = {"partial_fee": 10} - mocker.patch.object( - subtensor, - "get_neuron_for_pubkey_and_subnet", - return_value=NeuronInfo.get_null_neuron(), - ) - mocker.patch.object(subtensor, "get_balance") - - success, _ = subtensor.burned_register( - fake_wallet, - netuid=1, - ) - - assert success is True - - subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - netuid=1, - block=mock_substrate.get_block_number.return_value, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": 1, - "hotkey": fake_wallet.hotkey.ss58_address, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object( - subtensor, - "get_balance", - return_value=Balance(1), - ) - mocker.patch.object( - subtensor, - "is_hotkey_registered", - return_value=False, - ) - - success, _ = subtensor.burned_register( - fake_wallet, - netuid=0, - ) - - assert success is True - - subtensor.is_hotkey_registered.assert_called_once_with( - netuid=0, - hotkey_ss58=fake_wallet.hotkey.ss58_address, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="root_register", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_get_all_commitments(mock_substrate, subtensor): - mock_substrate.query_map.return_value = [ - ( - (tuple(bytearray(32)),), - { - "info": { - "fields": [ - ( - { - "Raw4": (tuple(b"Test"),), - }, - ), - ], - }, - }, - ), - ] - - result = subtensor.get_all_commitments(netuid=1) - - assert result == { - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": "Test", - } - - mock_substrate.query_map.assert_called_once_with( - module="Commitments", - storage_function="CommitmentOf", - params=[1], - block_hash=None, - ) - - -def test_get_balance(mock_substrate, subtensor): - mock_substrate.query.return_value = { - "data": { - "free": 123, - }, - } - - result = subtensor.get_balance( - "hotkey_ss58", - ) - - assert result == Balance(123) - - mock_substrate.query.assert_called_once_with( - module="System", - storage_function="Account", - params=["hotkey_ss58"], - block_hash=None, - ) - - -def test_get_balances(mock_substrate, subtensor, mocker): - create_storage_keys = [ - mocker.Mock(), - mocker.Mock(), - ] - - mock_substrate.create_storage_key.side_effect = create_storage_keys - mock_substrate.query_multi.return_value = [ - ( - mocker.Mock( - params=["hotkey1_ss58"], - ), - { - "data": { - "free": 1, - }, - }, - ), - ( - mocker.Mock( - params=["hotkey2_ss58"], - ), - { - "data": { - "free": 2, - }, - }, - ), - ] - - result = subtensor.get_balances( - "hotkey1_ss58", - "hotkey2_ss58", - ) - - assert result == { - "hotkey1_ss58": Balance(1), - "hotkey2_ss58": Balance(2), - } - - mock_substrate.query_multi.assert_called_once_with( - create_storage_keys, - block_hash=mock_substrate.get_chain_head.return_value, - ) - mock_substrate.create_storage_key.assert_has_calls( - [ - mocker.call( - "System", - "Account", - ["hotkey1_ss58"], - block_hash=mock_substrate.get_chain_head.return_value, - ), - mocker.call( - "System", - "Account", - ["hotkey2_ss58"], - block_hash=mock_substrate.get_chain_head.return_value, - ), - ] - ) - - -def test_get_block_hash_none(mock_substrate, subtensor): - result = subtensor.get_block_hash(block=None) - - assert result == mock_substrate.get_chain_head.return_value - - mock_substrate.get_chain_head.assert_called_once() - - -def test_get_children(mock_substrate, subtensor, fake_wallet): - mock_substrate.query.return_value.value = [ - ( - U64_MAX, - (tuple(bytearray(32)),), - ), - ] - - success, children, error = subtensor.get_children( - "hotkey_ss58", - netuid=1, - ) - - assert success is True - assert children == [ - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ] - assert error == "" - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="ChildKeys", - params=["hotkey_ss58", 1], - block_hash=None, - ) - - -def test_get_children_pending(mock_substrate, subtensor): - mock_substrate.query.return_value.value = [ - [ - ( - U64_MAX, - (tuple(bytearray(32)),), - ), - ], - 123, - ] - - children, cooldown = subtensor.get_children_pending( - "hotkey_ss58", - netuid=1, - ) - - assert children == [ - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ] - assert cooldown == 123 - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="PendingChildKeys", - params=[1, "hotkey_ss58"], - block_hash=None, - ) - - -def test_get_delegate_by_hotkey(mock_substrate, subtensor, mock_delegate_info): - mock_substrate.runtime_call.return_value.value = mock_delegate_info - - result = subtensor.get_delegate_by_hotkey( - "hotkey_ss58", - ) - - assert result == DelegateInfo( - hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - take=1.0, - validator_permits=[], - registrations=[], - return_per_1000=Balance(2), - total_daily_return=Balance(3), - total_stake={}, - nominators={}, - ) - - mock_substrate.runtime_call.assert_called_once_with( - "DelegateInfoRuntimeApi", - "get_delegate", - ["hotkey_ss58"], - None, - ) - - -def test_get_delegate_identities(mock_substrate, subtensor, mocker): - mock_substrate.query_map.return_value = [ - ( - (tuple(bytearray(32)),), - mocker.Mock( - value={ - "additional": "Additional", - "description": "Description", - "discord": "", - "github_repo": "https://github.com/opentensor/bittensor", - "image": "", - "name": "Chain Delegate", - "url": "https://www.example.com", - }, - ), - ), - ] - - result = subtensor.get_delegate_identities() - - assert result == { - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": ChainIdentity( - additional="Additional", - description="Description", - discord="", - github="https://github.com/opentensor/bittensor", - image="", - name="Chain Delegate", - url="https://www.example.com", - ), - } - - -def test_get_delegated(mock_substrate, subtensor, mock_delegate_info): - mock_substrate.runtime_call.return_value.value = [ - ( - mock_delegate_info, - ( - 0, - 999, - ), - ), - ] - - result = subtensor.get_delegated( - "coldkey_ss58", - ) - - assert result == [ - DelegatedInfo( - hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - take=1.0, - validator_permits=[], - registrations=[], - return_per_1000=Balance(2), - total_daily_return=Balance(3), - netuid=0, - stake=Balance(999), - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "DelegateInfoRuntimeApi", - "get_delegated", - ["coldkey_ss58"], - None, - ) - - -def test_get_neuron_certificate(mock_substrate, subtensor): - mock_substrate.query.return_value = { - "public_key": (tuple(b"CERTDATA"),), - "algorithm": 63, - } - - result = subtensor.get_neuron_certificate( - "hotkey_ss58", - netuid=1, - ) - - assert result == "?CERTDATA" - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="NeuronCertificates", - params=[1, "hotkey_ss58"], - block_hash=None, - ) - - -def test_get_stake_for_coldkey(mock_substrate, subtensor): - mock_substrate.runtime_call.return_value.value = [ - { - "coldkey": tuple(bytearray(32)), - "drain": 0, - "emission": 3, - "hotkey": tuple(bytearray(32)), - "is_registered": True, - "locked": 2, - "netuid": 1, - "stake": 999, - }, - # filter out (stake=0): - { - "coldkey": tuple(bytearray(32)), - "drain": 1000, - "emission": 1000, - "hotkey": tuple(bytearray(32)), - "is_registered": True, - "locked": 1000, - "netuid": 2, - "stake": 0, - }, - ] - - result = subtensor.get_stake_for_coldkey( - "coldkey_ss58", - ) - - assert result == [ - StakeInfo( - coldkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - drain=0, - emission=Balance(3), - hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - is_registered=True, - locked=Balance(2), - netuid=1, - stake=Balance(999), - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "StakeInfoRuntimeApi", - "get_stake_info_for_coldkey", - ["coldkey_ss58"], - None, - ) - - -def test_filter_netuids_by_registered_hotkeys( - mock_substrate, subtensor, fake_wallet, mocker -): - mock_substrate.query_map.return_value = mocker.MagicMock( - **{ - "__iter__.return_value": iter( - [ - ( - 2, - mocker.Mock( - value=1, - ), - ), - ( - 3, - mocker.Mock( - value=1, - ), - ), - ] - ), - }, - ) - - result = subtensor.filter_netuids_by_registered_hotkeys( - all_netuids=[0, 1, 2], - filter_for_netuids=[2], - all_hotkeys=[fake_wallet], - block=10, - ) - - assert result == [2] - - mock_substrate.get_block_hash.assert_called_once_with(10) - mock_substrate.query_map.assert_called_once_with( - module="SubtensorModule", - storage_function="IsNetworkMember", - params=[fake_wallet.hotkey.ss58_address], - block_hash=mock_substrate.get_block_hash.return_value, - ) - - -def test_last_drand_round(mock_substrate, subtensor): - mock_substrate.query.return_value.value = 123 - - result = subtensor.last_drand_round() - - assert result == 123 - - mock_substrate.query.assert_called_once_with( - module="Drand", - storage_function="LastStoredRound", - ) - - -@pytest.mark.parametrize( - "wait,resp_message", - ( - [True, "Success"], - [False, "Not waiting for finalization or inclusion."], - ), -) -def test_move_stake(mock_substrate, subtensor, fake_wallet, wait, resp_message): - success, message = subtensor.move_stake( - wallet=fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - assert success is True - assert message == resp_message - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": "origin_hotkey", - "origin_netuid": 1, - "destination_hotkey": "destination_hotkey", - "destination_netuid": 2, - "alpha_amount": 1, - }, - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - -def test_move_stake_insufficient_stake(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_stake", return_value=Balance(0)) - - success, message = subtensor.move_stake( - fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - ) - - assert success is False - assert "Insufficient stake in origin hotkey" in message - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - error_message="ERROR", - is_success=False, - ) - - success, message = subtensor.move_stake( - fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - ) - - assert success is False - assert ( - message - == "Subtensor returned `UnknownError(UnknownType)` error. This means: `Unknown Description`." - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": "origin_hotkey", - "origin_netuid": 1, - "destination_hotkey": "destination_hotkey", - "destination_netuid": 2, - "alpha_amount": 1, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_move_stake_exception(mock_substrate, subtensor, fake_wallet): - mock_substrate.submit_extrinsic.side_effect = RuntimeError - - with pytest.raises(RuntimeError) as exc: - subtensor.move_stake( - fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - raise_error=True, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": "origin_hotkey", - "origin_netuid": 1, - "destination_hotkey": "destination_hotkey", - "destination_netuid": 2, - "alpha_amount": 1, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_neurons(mock_substrate, subtensor, mock_neuron_info): - mock_substrate.runtime_call.return_value.value = [ - mock_neuron_info, - ] - - neurons = subtensor.neurons(netuid=1) - - assert neurons == [ - NeuronInfo( - axon_info=AxonInfo( - version=1, - ip="127.0.0.1", - port=8080, - ip_type=4, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - active=0, - bonds=[], - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - consensus=0.0, - dividends=0.0, - emission=0.0, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - incentive=0.0, - is_null=False, - last_update=0, - netuid=1, - prometheus_info=PrometheusInfo( - block=0, - version=1, - ip="0.0.0.0", - port=0, - ip_type=0, - ), - pruning_score=0.0, - rank=0.0, - stake_dict={}, - stake=Balance(0), - total_stake=Balance(0), - trust=0.0, - uid=1, - validator_permit=True, - validator_trust=0.0, - weights=[], - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "NeuronInfoRuntimeApi", - "get_neurons", - [1], - None, - ) - - -def test_neurons_lite(mock_substrate, subtensor, mock_neuron_info): - mock_substrate.runtime_call.return_value.value = [ - mock_neuron_info, - ] - - result = subtensor.neurons_lite(netuid=1) - - assert result == [ - NeuronInfoLite( - axon_info=AxonInfo( - version=1, - ip="127.0.0.1", - port=8080, - ip_type=4, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - active=0, - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - consensus=0.0, - dividends=0.0, - emission=0.0, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - incentive=0.0, - is_null=False, - last_update=0, - netuid=1, - prometheus_info=PrometheusInfo( - block=0, - version=1, - ip="0.0.0.0", - port=0, - ip_type=0, - ), - pruning_score=0.0, - rank=0.0, - stake_dict={}, - stake=Balance(0), - total_stake=Balance(0), - trust=0.0, - uid=1, - validator_permit=True, - validator_trust=0.0, - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "NeuronInfoRuntimeApi", - "get_neurons_lite", - [1], - None, - ) - - -def test_set_children(mock_substrate, subtensor, fake_wallet): - subtensor.set_children( - fake_wallet, - fake_wallet.hotkey.ss58_address, - netuid=1, - children=[ - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ], - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - U64_MAX, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ) - ], - "hotkey": fake_wallet.hotkey.ss58_address, - "netuid": 1, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -def test_set_delegate_take_equal(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.18, - ).success - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_set_delegate_take_increase(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.2, - ).success - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 13107, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -def test_set_delegate_take_decrease(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.1, - ).success - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 6553, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -def test_subnet(mock_substrate, subtensor, mock_dynamic_info): - mock_substrate.runtime_call.return_value.decode.return_value = mock_dynamic_info - - result = subtensor.subnet(netuid=0) - - assert result == DynamicInfo( - netuid=0, - owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - subnet_name="root", - symbol="Τ", - tempo=100, - last_step=4919910, - blocks_since_last_step=84234, - emission=Balance(0), - alpha_in=Balance(14723086336554), - alpha_out=Balance(6035890271491007), - tao_in=Balance(6035892206947246), - price=Balance.from_tao(1), - k=88866962081017766138079430284, - is_dynamic=False, - alpha_out_emission=Balance(0), - alpha_in_emission=Balance(0), - tao_in_emission=Balance(0), - pending_alpha_emission=Balance(0), - pending_root_emission=Balance(0), - network_registered_at=0, - subnet_volume=Balance(2240411565906691), - subnet_identity=None, - moving_price=0.0, - ) - - mock_substrate.runtime_call.assert_called_once_with( - "SubnetInfoRuntimeApi", - "get_dynamic_info", - params=[0], - block_hash=None, - ) - - -def test_subtensor_contextmanager(mock_substrate, subtensor): - with subtensor: - pass - - mock_substrate.close.assert_called_once() - - -def test_swap_stake(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_stake", return_value=Balance(1000)) - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value=fake_wallet.coldkeypub.ss58_address, - ) - - success, message = subtensor.swap_stake( - fake_wallet, - fake_wallet.hotkey.ss58_address, - origin_netuid=1, - destination_netuid=2, - amount=Balance(999), - ) - - assert success is True - assert message == "Success" - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="swap_stake", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "origin_netuid": 1, - "destination_netuid": 2, - "alpha_amount": 999, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -@pytest.mark.parametrize( - "query,result", - ( - ( - None, - None, - ), - ( - { - "additional": "Additional", - "description": "Description", - "discord": "", - "github_repo": "https://github.com/opentensor/bittensor", - "image": "", - "name": "Chain Delegate", - "url": "https://www.example.com", - }, - ChainIdentity( - additional="Additional", - description="Description", - discord="", - github="https://github.com/opentensor/bittensor", - image="", - name="Chain Delegate", - url="https://www.example.com", - ), - ), - ), -) -def test_query_identity(mock_substrate, subtensor, query, result): - mock_substrate.query.return_value = query - - identity = subtensor.query_identity( - "coldkey_ss58", - ) - - assert identity == result - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="IdentitiesV2", - params=["coldkey_ss58"], - block_hash=None, - ) - - -def test_register(mock_substrate, subtensor, fake_wallet, mocker): - create_pow = mocker.patch( - "bittensor.core.extrinsics.registration.create_pow", - return_value=mocker.Mock( - **{ - "is_stale.return_value": False, - "seal": b"\1\2\3", - }, - ), - ) - mocker.patch.object( - subtensor, - "get_neuron_for_pubkey_and_subnet", - return_value=NeuronInfo.get_null_neuron(), - ) - - assert subtensor.register( - wallet=fake_wallet, - netuid=1, - ).success - - subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( - hotkey_ss58=fake_wallet.hotkey.ss58_address, - netuid=1, - block=mock_substrate.get_block_number.return_value, - ) - create_pow.assert_called_once_with( - subtensor=subtensor, - wallet=fake_wallet, - netuid=1, - output_in_place=True, - cuda=False, - num_processes=None, - update_interval=None, - log_verbose=False, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="register", - call_params={ - "block_number": create_pow.return_value.block_number, - "coldkey": fake_wallet.coldkeypub.ss58_address, - "hotkey": fake_wallet.hotkey.ss58_address, - "netuid": 1, - "nonce": create_pow.return_value.nonce, - "work": [1, 2, 3], - }, - ) - - -@pytest.mark.parametrize( - "success", - [ - True, - False, - ], -) -def test_register_subnet(mock_substrate, subtensor, fake_wallet, mocker, success): - mocker.patch.object(subtensor, "get_balance", return_value=Balance(100)) - mocker.patch.object(subtensor, "get_subnet_burn_cost", return_value=Balance(10)) - - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - is_success=success, - ) - - result = subtensor.register_subnet(fake_wallet) - - assert result.success is success - - assert_submit_signed_extrinsic( - substrate=mock_substrate, - keypair=fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - - -def test_register_subnet_insufficient_funds( - mock_substrate, subtensor, fake_wallet, mocker -): - mocker.patch.object(subtensor, "get_balance", return_value=Balance(0)) - mocker.patch.object(subtensor, "get_subnet_burn_cost", return_value=Balance(10)) - - success, _ = subtensor.register_subnet(fake_wallet) - - assert success is False - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_root_register(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object( - subtensor, "get_balance", autospec=True, return_value=Balance(100) - ) - mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) - mocker.patch.object( - subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=False - ) - - success, _ = subtensor.root_register(fake_wallet) - - assert success is True - - subtensor.get_balance.assert_called_once_with( - fake_wallet.coldkeypub.ss58_address, - block=mock_substrate.get_block_number.return_value, - ) - subtensor.get_hyperparameter.assert_called_once() - subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - 0, - None, - ) - - assert_submit_signed_extrinsic( - substrate=mock_substrate, - keypair=fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="root_register", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - }, - ) - - -def test_root_register_is_already_registered( - mock_substrate, subtensor, fake_wallet, mocker -): - mocker.patch.object( - subtensor, "get_balance", autospec=True, return_value=Balance(100) - ) - mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) - mocker.patch.object( - subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=True - ) - - success, _ = subtensor.root_register(fake_wallet) - - assert success is True - - subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - 0, - None, - ) - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_sign_and_send_extrinsic(mock_substrate, subtensor, fake_wallet, mocker): - call = mocker.Mock() - - subtensor.sign_and_send_extrinsic( - call=call, - wallet=fake_wallet, - use_nonce=True, - period=10, - ) - - mock_substrate.get_account_next_index.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - ) - mock_substrate.create_signed_extrinsic.assert_called_once_with( - call=call, - era={ - "period": 10, - }, - keypair=fake_wallet.coldkey, - nonce=mock_substrate.get_account_next_index.return_value, - ) - mock_substrate.submit_extrinsic.assert_called_once_with( - mock_substrate.create_signed_extrinsic.return_value, - wait_for_inclusion=True, - wait_for_finalization=False, - ) - - -def test_sign_and_send_extrinsic_raises_error( - mock_substrate, subtensor, fake_wallet, mocker -): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - error_message={ - "name": "Exception", - }, - is_success=False, - ) - - with pytest.raises( - async_substrate_interface.errors.SubstrateRequestException, - match="{'name': 'Exception'}", - ): - subtensor.sign_and_send_extrinsic( - call=mocker.Mock(), - wallet=fake_wallet, - raise_error=True, - ) - - -@pytest.mark.parametrize( - "wait,response_message", - ( - [True, "Success"], - [False, "Not waiting for finalization or inclusion."], - ), -) -def test_transfer_stake( - mock_substrate, subtensor, fake_wallet, mocker, wait, response_message -): - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value=fake_wallet.coldkeypub.ss58_address, - ) - - success, message = subtensor.transfer_stake( - fake_wallet, - "dest", - "hotkey_ss58", - origin_netuid=1, - destination_netuid=1, - amount=Balance(1), - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - assert success is True - assert message == response_message - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": "dest", - "hotkey": "hotkey_ss58", - "origin_netuid": 1, - "destination_netuid": 1, - "alpha_amount": 1, - }, - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - -def test_transfer_stake_insufficient_stake( - mock_substrate, subtensor, fake_wallet, mocker -): - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value=fake_wallet.coldkeypub.ss58_address, - ) - - with unittest.mock.patch.object( - subtensor, - "get_stake", - return_value=Balance(0), - ): - success, message = subtensor.transfer_stake( - fake_wallet, - "dest", - "hotkey_ss58", - origin_netuid=1, - destination_netuid=1, - amount=Balance(1), - ) - - assert success is False - assert "Insufficient stake in origin hotkey" in message - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_wait_for_block(mock_substrate, subtensor, mocker): - mock_subscription_handler = None - - def get_block_handler( - current_block_hash, - header_only, - subscription_handler, - ): - nonlocal mock_subscription_handler - mock_subscription_handler = mocker.Mock(wraps=subscription_handler) - - for block in range(1, 20): - if mock_subscription_handler( - { - "header": { - "number": block, - }, - } - ): - return - - assert False - - mock_substrate.get_block.side_effect = [ - { - "header": { - "number": 1, - }, - }, - ] - mock_substrate.get_block_handler.side_effect = get_block_handler - - subtensor.wait_for_block(block=9) - - assert mock_subscription_handler.call_count == 9 - - -def test_weights(mock_substrate, subtensor): - mock_substrate.query_map.return_value = [ - (1, unittest.mock.Mock(value=0.5)), - ] - - results = subtensor.weights( - netuid=1, - ) - - assert results == [ - ( - 1, - 0.5, - ), - ] - - mock_substrate.query_map.assert_called_once_with( - module="SubtensorModule", - storage_function="Weights", - params=[1], - block_hash=None, - ) +# import unittest.mock +# +# import async_substrate_interface.errors +# import pytest +# +# from bittensor.core.chain_data.axon_info import AxonInfo +# from bittensor.core.chain_data.chain_identity import ChainIdentity +# from bittensor.core.chain_data.delegate_info import DelegatedInfo, DelegateInfo +# from bittensor.core.chain_data.dynamic_info import DynamicInfo +# from bittensor.core.chain_data.neuron_info import NeuronInfo +# from bittensor.core.chain_data.neuron_info_lite import NeuronInfoLite +# from bittensor.core.chain_data.prometheus_info import PrometheusInfo +# from bittensor.core.chain_data.stake_info import StakeInfo +# from bittensor.utils import U16_MAX, U64_MAX +# from bittensor.utils.balance import Balance +# from tests.helpers.helpers import assert_submit_signed_extrinsic +# from bittensor.core import subtensor as subtensor_module +# +# +# @pytest.fixture +# def mock_delegate_info(): +# return { +# "delegate_ss58": tuple(bytearray(32)), +# "total_stake": {}, +# "nominators": [], +# "owner_ss58": tuple(bytearray(32)), +# "take": U16_MAX, +# "validator_permits": [], +# "registrations": [], +# "return_per_1000": 2, +# "total_daily_return": 3, +# } +# +# +# @pytest.fixture +# def mock_dynamic_info(): +# return { +# "netuid": 0, +# "owner_hotkey": tuple(bytearray(32)), +# "owner_coldkey": tuple(bytearray(32)), +# "subnet_name": (114, 111, 111, 116), +# "token_symbol": (206, 164), +# "tempo": 100, +# "last_step": 4919910, +# "blocks_since_last_step": 84234, +# "emission": 0, +# "alpha_in": 14723086336554, +# "alpha_out": 6035890271491007, +# "tao_in": 6035892206947246, +# "alpha_out_emission": 0, +# "alpha_in_emission": 0, +# "tao_in_emission": 0, +# "pending_alpha_emission": 0, +# "pending_root_emission": 0, +# "subnet_volume": 2240411565906691, +# "network_registered_at": 0, +# "subnet_identity": None, +# "moving_price": {"bits": 0}, +# } +# +# +# @pytest.fixture +# def mock_neuron_info(): +# return { +# "active": 0, +# "axon_info": { +# "ip_type": 4, +# "ip": 2130706433, +# "placeholder1": 0, +# "placeholder2": 0, +# "port": 8080, +# "protocol": 0, +# "version": 1, +# }, +# "bonds": [], +# "coldkey": tuple(bytearray(32)), +# "consensus": 0.0, +# "dividends": 0.0, +# "emission": 0.0, +# "hotkey": tuple(bytearray(32)), +# "incentive": 0.0, +# "is_null": False, +# "last_update": 0, +# "netuid": 1, +# "prometheus_info": { +# "block": 0, +# "ip_type": 0, +# "ip": 0, +# "port": 0, +# "version": 1, +# }, +# "pruning_score": 0.0, +# "rank": 0.0, +# "stake_dict": {}, +# "stake": [], +# "total_stake": 1e12, +# "trust": 0.0, +# "uid": 1, +# "validator_permit": True, +# "validator_trust": 0.0, +# "weights": [], +# } +# +# +# def test_all_subnets(mock_substrate, subtensor, mock_dynamic_info): +# mock_substrate.runtime_call.return_value.decode.return_value = [ +# mock_dynamic_info, +# ] +# +# result = subtensor.all_subnets() +# +# assert result == [ +# DynamicInfo( +# netuid=0, +# owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# subnet_name="root", +# symbol="Τ", +# tempo=100, +# last_step=4919910, +# blocks_since_last_step=84234, +# emission=Balance(0), +# alpha_in=Balance(14723086336554), +# alpha_out=Balance(6035890271491007), +# tao_in=Balance(6035892206947246), +# price=Balance.from_tao(1), +# k=88866962081017766138079430284, +# is_dynamic=False, +# alpha_out_emission=Balance(0), +# alpha_in_emission=Balance(0), +# tao_in_emission=Balance(0), +# pending_alpha_emission=Balance(0), +# pending_root_emission=Balance(0), +# network_registered_at=0, +# subnet_volume=Balance(2240411565906691), +# subnet_identity=None, +# moving_price=0.0, +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "SubnetInfoRuntimeApi", +# "get_all_dynamic_info", +# block_hash=None, +# ) +# +# +# def test_bonds(mock_substrate, subtensor, mocker): +# mock_substrate.query_map.return_value = [ +# (0, mocker.Mock(value=[(1, 100), (2, 200)])), +# (1, mocker.Mock(value=[(0, 150), (2, 250)])), +# (2, mocker.Mock(value=None)), +# ] +# +# result = subtensor.bonds(netuid=1) +# +# assert result == [ +# (0, [(1, 100), (2, 200)]), +# (1, [(0, 150), (2, 250)]), +# ] +# +# mock_substrate.query_map.assert_called_once_with( +# module="SubtensorModule", +# storage_function="Bonds", +# params=[1], +# block_hash=None, +# ) +# +# +# def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): +# mock_substrate.get_payment_info.return_value = {"partial_fee": 10} +# mocker.patch.object( +# subtensor, +# "get_neuron_for_pubkey_and_subnet", +# return_value=NeuronInfo.get_null_neuron(), +# ) +# mocker.patch.object(subtensor, "get_balance") +# +# success, _ = subtensor.burned_register( +# fake_wallet, +# netuid=1, +# ) +# +# assert success is True +# +# subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# netuid=1, +# block=mock_substrate.get_block_number.return_value, +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="burned_register", +# call_params={ +# "netuid": 1, +# "hotkey": fake_wallet.hotkey.ss58_address, +# }, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# +# +# def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker): +# mocked_root_register_extrinsic = mocker.patch.object( +# subtensor_module, +# "root_register_extrinsic", +# ) +# result = subtensor.burned_register( +# wallet=fake_wallet, +# netuid=0, +# ) +# +# assert result == mocked_root_register_extrinsic.return_value +# +# +# def test_get_all_commitments(mock_substrate, subtensor): +# mock_substrate.query_map.return_value = [ +# ( +# (tuple(bytearray(32)),), +# { +# "info": { +# "fields": [ +# ( +# { +# "Raw4": (tuple(b"Test"),), +# }, +# ), +# ], +# }, +# }, +# ), +# ] +# +# result = subtensor.get_all_commitments(netuid=1) +# +# assert result == { +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": "Test", +# } +# +# mock_substrate.query_map.assert_called_once_with( +# module="Commitments", +# storage_function="CommitmentOf", +# params=[1], +# block_hash=None, +# ) +# +# +# def test_get_balance(mock_substrate, subtensor): +# mock_substrate.query.return_value = { +# "data": { +# "free": 123, +# }, +# } +# +# result = subtensor.get_balance( +# "hotkey_ss58", +# ) +# +# assert result == Balance(123) +# +# mock_substrate.query.assert_called_once_with( +# module="System", +# storage_function="Account", +# params=["hotkey_ss58"], +# block_hash=None, +# ) +# +# +# def test_get_balances(mock_substrate, subtensor, mocker): +# create_storage_keys = [ +# mocker.Mock(), +# mocker.Mock(), +# ] +# +# mock_substrate.create_storage_key.side_effect = create_storage_keys +# mock_substrate.query_multi.return_value = [ +# ( +# mocker.Mock( +# params=["hotkey1_ss58"], +# ), +# { +# "data": { +# "free": 1, +# }, +# }, +# ), +# ( +# mocker.Mock( +# params=["hotkey2_ss58"], +# ), +# { +# "data": { +# "free": 2, +# }, +# }, +# ), +# ] +# +# result = subtensor.get_balances( +# "hotkey1_ss58", +# "hotkey2_ss58", +# ) +# +# assert result == { +# "hotkey1_ss58": Balance(1), +# "hotkey2_ss58": Balance(2), +# } +# +# mock_substrate.query_multi.assert_called_once_with( +# create_storage_keys, +# block_hash=mock_substrate.get_chain_head.return_value, +# ) +# mock_substrate.create_storage_key.assert_has_calls( +# [ +# mocker.call( +# "System", +# "Account", +# ["hotkey1_ss58"], +# block_hash=mock_substrate.get_chain_head.return_value, +# ), +# mocker.call( +# "System", +# "Account", +# ["hotkey2_ss58"], +# block_hash=mock_substrate.get_chain_head.return_value, +# ), +# ] +# ) +# +# +# def test_get_block_hash_none(mock_substrate, subtensor): +# result = subtensor.get_block_hash(block=None) +# +# assert result == mock_substrate.get_chain_head.return_value +# +# mock_substrate.get_chain_head.assert_called_once() +# +# +# def test_get_children(mock_substrate, subtensor, fake_wallet): +# mock_substrate.query.return_value.value = [ +# ( +# U64_MAX, +# (tuple(bytearray(32)),), +# ), +# ] +# +# success, children, error = subtensor.get_children( +# "hotkey_ss58", +# netuid=1, +# ) +# +# assert success is True +# assert children == [ +# ( +# 1.0, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# ] +# assert error == "" +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="ChildKeys", +# params=["hotkey_ss58", 1], +# block_hash=None, +# ) +# +# +# def test_get_children_pending(mock_substrate, subtensor): +# mock_substrate.query.return_value.value = [ +# [ +# ( +# U64_MAX, +# (tuple(bytearray(32)),), +# ), +# ], +# 123, +# ] +# +# children, cooldown = subtensor.get_children_pending( +# "hotkey_ss58", +# netuid=1, +# ) +# +# assert children == [ +# ( +# 1.0, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# ] +# assert cooldown == 123 +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="PendingChildKeys", +# params=[1, "hotkey_ss58"], +# block_hash=None, +# ) +# +# +# def test_get_delegate_by_hotkey(mock_substrate, subtensor, mock_delegate_info): +# mock_substrate.runtime_call.return_value.value = mock_delegate_info +# +# result = subtensor.get_delegate_by_hotkey( +# "hotkey_ss58", +# ) +# +# assert result == DelegateInfo( +# hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# take=1.0, +# validator_permits=[], +# registrations=[], +# return_per_1000=Balance(2), +# total_daily_return=Balance(3), +# total_stake={}, +# nominators={}, +# ) +# +# mock_substrate.runtime_call.assert_called_once_with( +# "DelegateInfoRuntimeApi", +# "get_delegate", +# ["hotkey_ss58"], +# None, +# ) +# +# +# def test_get_delegate_identities(mock_substrate, subtensor, mocker): +# mock_substrate.query_map.return_value = [ +# ( +# (tuple(bytearray(32)),), +# mocker.Mock( +# value={ +# "additional": "Additional", +# "description": "Description", +# "discord": "", +# "github_repo": "https://github.com/opentensor/bittensor", +# "image": "", +# "name": "Chain Delegate", +# "url": "https://www.example.com", +# }, +# ), +# ), +# ] +# +# result = subtensor.get_delegate_identities() +# +# assert result == { +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": ChainIdentity( +# additional="Additional", +# description="Description", +# discord="", +# github="https://github.com/opentensor/bittensor", +# image="", +# name="Chain Delegate", +# url="https://www.example.com", +# ), +# } +# +# +# def test_get_delegated(mock_substrate, subtensor, mock_delegate_info): +# mock_substrate.runtime_call.return_value.value = [ +# ( +# mock_delegate_info, +# ( +# 0, +# 999, +# ), +# ), +# ] +# +# result = subtensor.get_delegated( +# "coldkey_ss58", +# ) +# +# assert result == [ +# DelegatedInfo( +# hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# take=1.0, +# validator_permits=[], +# registrations=[], +# return_per_1000=Balance(2), +# total_daily_return=Balance(3), +# netuid=0, +# stake=Balance(999), +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "DelegateInfoRuntimeApi", +# "get_delegated", +# ["coldkey_ss58"], +# None, +# ) +# +# +# def test_get_neuron_certificate(mock_substrate, subtensor): +# mock_substrate.query.return_value = { +# "public_key": (tuple(b"CERTDATA"),), +# "algorithm": 63, +# } +# +# result = subtensor.get_neuron_certificate( +# "hotkey_ss58", +# netuid=1, +# ) +# +# assert result == "?CERTDATA" +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="NeuronCertificates", +# params=[1, "hotkey_ss58"], +# block_hash=None, +# ) +# +# +# def test_get_stake_for_coldkey(mock_substrate, subtensor): +# mock_substrate.runtime_call.return_value.value = [ +# { +# "coldkey": tuple(bytearray(32)), +# "drain": 0, +# "emission": 3, +# "hotkey": tuple(bytearray(32)), +# "is_registered": True, +# "locked": 2, +# "netuid": 1, +# "stake": 999, +# }, +# # filter out (stake=0): +# { +# "coldkey": tuple(bytearray(32)), +# "drain": 1000, +# "emission": 1000, +# "hotkey": tuple(bytearray(32)), +# "is_registered": True, +# "locked": 1000, +# "netuid": 2, +# "stake": 0, +# }, +# ] +# +# result = subtensor.get_stake_for_coldkey( +# "coldkey_ss58", +# ) +# +# assert result == [ +# StakeInfo( +# coldkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# drain=0, +# emission=Balance(3), +# hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# is_registered=True, +# locked=Balance(2), +# netuid=1, +# stake=Balance(999), +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "StakeInfoRuntimeApi", +# "get_stake_info_for_coldkey", +# ["coldkey_ss58"], +# None, +# ) +# +# +# def test_filter_netuids_by_registered_hotkeys( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mock_substrate.query_map.return_value = mocker.MagicMock( +# **{ +# "__iter__.return_value": iter( +# [ +# ( +# 2, +# mocker.Mock( +# value=1, +# ), +# ), +# ( +# 3, +# mocker.Mock( +# value=1, +# ), +# ), +# ] +# ), +# }, +# ) +# +# result = subtensor.filter_netuids_by_registered_hotkeys( +# all_netuids=[0, 1, 2], +# filter_for_netuids=[2], +# all_hotkeys=[fake_wallet], +# block=10, +# ) +# +# assert result == [2] +# +# mock_substrate.get_block_hash.assert_called_once_with(10) +# mock_substrate.query_map.assert_called_once_with( +# module="SubtensorModule", +# storage_function="IsNetworkMember", +# params=[fake_wallet.hotkey.ss58_address], +# block_hash=mock_substrate.get_block_hash.return_value, +# ) +# +# +# def test_last_drand_round(mock_substrate, subtensor): +# mock_substrate.query.return_value.value = 123 +# +# result = subtensor.last_drand_round() +# +# assert result == 123 +# +# mock_substrate.query.assert_called_once_with( +# module="Drand", +# storage_function="LastStoredRound", +# ) +# +# +# @pytest.mark.parametrize( +# "wait,resp_message", +# ( +# [True, "Success"], +# [False, "Not waiting for finalization or inclusion."], +# ), +# ) +# def test_move_stake(mock_substrate, subtensor, fake_wallet, wait, resp_message): +# success, message = subtensor.move_stake( +# wallet=fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# wait_for_finalization=wait, +# wait_for_inclusion=wait, +# ) +# +# assert success is True +# assert message == resp_message +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="move_stake", +# call_params={ +# "origin_hotkey": "origin_hotkey", +# "origin_netuid": 1, +# "destination_hotkey": "destination_hotkey", +# "destination_netuid": 2, +# "alpha_amount": 1, +# }, +# wait_for_finalization=wait, +# wait_for_inclusion=wait, +# ) +# +# +# def test_move_stake_insufficient_stake(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_stake", return_value=Balance(0)) +# +# success, message = subtensor.move_stake( +# fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# ) +# +# assert success is False +# assert "Insufficient stake in origin hotkey" in message +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): +# mock_substrate.submit_extrinsic.return_value = mocker.Mock( +# error_message="ERROR", +# is_success=False, +# ) +# +# success, message = subtensor.move_stake( +# fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# ) +# +# assert success is False +# assert ( +# message +# == "Subtensor returned `UnknownError(UnknownType)` error. This means: `Unknown Description`." +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="move_stake", +# call_params={ +# "origin_hotkey": "origin_hotkey", +# "origin_netuid": 1, +# "destination_hotkey": "destination_hotkey", +# "destination_netuid": 2, +# "alpha_amount": 1, +# }, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# +# +# def test_move_stake_exception(mock_substrate, subtensor, fake_wallet): +# mock_substrate.submit_extrinsic.side_effect = RuntimeError +# +# with pytest.raises(RuntimeError) as exc: +# subtensor.move_stake( +# fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# raise_error=True, +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="move_stake", +# call_params={ +# "origin_hotkey": "origin_hotkey", +# "origin_netuid": 1, +# "destination_hotkey": "destination_hotkey", +# "destination_netuid": 2, +# "alpha_amount": 1, +# }, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# +# +# def test_neurons(mock_substrate, subtensor, mock_neuron_info): +# mock_substrate.runtime_call.return_value.value = [ +# mock_neuron_info, +# ] +# +# neurons = subtensor.neurons(netuid=1) +# +# assert neurons == [ +# NeuronInfo( +# axon_info=AxonInfo( +# version=1, +# ip="127.0.0.1", +# port=8080, +# ip_type=4, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# active=0, +# bonds=[], +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# consensus=0.0, +# dividends=0.0, +# emission=0.0, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# incentive=0.0, +# is_null=False, +# last_update=0, +# netuid=1, +# prometheus_info=PrometheusInfo( +# block=0, +# version=1, +# ip="0.0.0.0", +# port=0, +# ip_type=0, +# ), +# pruning_score=0.0, +# rank=0.0, +# stake_dict={}, +# stake=Balance(0), +# total_stake=Balance(0), +# trust=0.0, +# uid=1, +# validator_permit=True, +# validator_trust=0.0, +# weights=[], +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "NeuronInfoRuntimeApi", +# "get_neurons", +# [1], +# None, +# ) +# +# +# def test_neurons_lite(mock_substrate, subtensor, mock_neuron_info): +# mock_substrate.runtime_call.return_value.value = [ +# mock_neuron_info, +# ] +# +# result = subtensor.neurons_lite(netuid=1) +# +# assert result == [ +# NeuronInfoLite( +# axon_info=AxonInfo( +# version=1, +# ip="127.0.0.1", +# port=8080, +# ip_type=4, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# active=0, +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# consensus=0.0, +# dividends=0.0, +# emission=0.0, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# incentive=0.0, +# is_null=False, +# last_update=0, +# netuid=1, +# prometheus_info=PrometheusInfo( +# block=0, +# version=1, +# ip="0.0.0.0", +# port=0, +# ip_type=0, +# ), +# pruning_score=0.0, +# rank=0.0, +# stake_dict={}, +# stake=Balance(0), +# total_stake=Balance(0), +# trust=0.0, +# uid=1, +# validator_permit=True, +# validator_trust=0.0, +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "NeuronInfoRuntimeApi", +# "get_neurons_lite", +# [1], +# None, +# ) +# +# +# def test_set_children(mock_substrate, subtensor, fake_wallet): +# subtensor.set_children( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# netuid=1, +# children=[ +# ( +# 1.0, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# ], +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="set_children", +# call_params={ +# "children": [ +# ( +# U64_MAX, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ) +# ], +# "hotkey": fake_wallet.hotkey.ss58_address, +# "netuid": 1, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_set_delegate_take_equal(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) +# +# assert subtensor.set_delegate_take( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# 0.18, +# ).success +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_set_delegate_take_increase(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) +# +# assert subtensor.set_delegate_take( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# 0.2, +# ).success +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="increase_take", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# "take": 13107, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_set_delegate_take_decrease(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) +# +# assert subtensor.set_delegate_take( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# 0.1, +# ).success +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="decrease_take", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# "take": 6553, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_subnet(mock_substrate, subtensor, mock_dynamic_info): +# mock_substrate.runtime_call.return_value.decode.return_value = mock_dynamic_info +# +# result = subtensor.subnet(netuid=0) +# +# assert result == DynamicInfo( +# netuid=0, +# owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# subnet_name="root", +# symbol="Τ", +# tempo=100, +# last_step=4919910, +# blocks_since_last_step=84234, +# emission=Balance(0), +# alpha_in=Balance(14723086336554), +# alpha_out=Balance(6035890271491007), +# tao_in=Balance(6035892206947246), +# price=Balance.from_tao(1), +# k=88866962081017766138079430284, +# is_dynamic=False, +# alpha_out_emission=Balance(0), +# alpha_in_emission=Balance(0), +# tao_in_emission=Balance(0), +# pending_alpha_emission=Balance(0), +# pending_root_emission=Balance(0), +# network_registered_at=0, +# subnet_volume=Balance(2240411565906691), +# subnet_identity=None, +# moving_price=0.0, +# ) +# +# mock_substrate.runtime_call.assert_called_once_with( +# "SubnetInfoRuntimeApi", +# "get_dynamic_info", +# params=[0], +# block_hash=None, +# ) +# +# +# def test_subtensor_contextmanager(mock_substrate, subtensor): +# with subtensor: +# pass +# +# mock_substrate.close.assert_called_once() +# +# +# def test_swap_stake(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_stake", return_value=Balance(1000)) +# mocker.patch.object( +# subtensor, +# "get_hotkey_owner", +# autospec=True, +# return_value=fake_wallet.coldkeypub.ss58_address, +# ) +# +# success, message = subtensor.swap_stake( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# origin_netuid=1, +# destination_netuid=2, +# amount=Balance(999), +# ) +# +# assert success is True +# assert message == "Success" +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="swap_stake", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# "origin_netuid": 1, +# "destination_netuid": 2, +# "alpha_amount": 999, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# @pytest.mark.parametrize( +# "query,result", +# ( +# ( +# None, +# None, +# ), +# ( +# { +# "additional": "Additional", +# "description": "Description", +# "discord": "", +# "github_repo": "https://github.com/opentensor/bittensor", +# "image": "", +# "name": "Chain Delegate", +# "url": "https://www.example.com", +# }, +# ChainIdentity( +# additional="Additional", +# description="Description", +# discord="", +# github="https://github.com/opentensor/bittensor", +# image="", +# name="Chain Delegate", +# url="https://www.example.com", +# ), +# ), +# ), +# ) +# def test_query_identity(mock_substrate, subtensor, query, result): +# mock_substrate.query.return_value = query +# +# identity = subtensor.query_identity( +# "coldkey_ss58", +# ) +# +# assert identity == result +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="IdentitiesV2", +# params=["coldkey_ss58"], +# block_hash=None, +# ) +# +# +# def \ +# test_register(mock_substrate, subtensor, fake_wallet, mocker): +# create_pow = mocker.patch( +# "bittensor.core.extrinsics.registration.create_pow", +# return_value=mocker.Mock( +# **{ +# "is_stale.return_value": False, +# "seal": b"\1\2\3", +# }, +# ), +# ) +# mocker.patch.object( +# subtensor, +# "get_neuron_for _pubkey_and_subnet", +# return_value=NeuronInfo.get_null_neuron(), +# ) +# +# assert subtensor.register( +# wallet=fake_wallet, +# netuid=1, +# ).success +# +# subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( +# hotkey_ss58=fake_wallet.hotkey.ss58_address, +# netuid=1, +# block=mock_substrate.get_block_number.return_value, +# ) +# create_pow.assert_called_once_with( +# subtensor=subtensor, +# wallet=fake_wallet, +# netuid=1, +# output_in_place=True, +# cuda=False, +# num_processes=None, +# update_interval=None, +# log_verbose=False, +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="register", +# call_params={ +# "block_number": create_pow.return_value.block_number, +# "coldkey": fake_wallet.coldkeypub.ss58_address, +# "hotkey": fake_wallet.hotkey.ss58_address, +# "netuid": 1, +# "nonce": create_pow.return_value.nonce, +# "work": [1, 2, 3], +# }, +# ) +# +# +# @pytest.mark.parametrize( +# "success", +# [ +# True, +# False, +# ], +# ) +# def test_register_subnet(mock_substrate, subtensor, fake_wallet, mocker, success): +# mocker.patch.object(subtensor, "get_balance", return_value=Balance(100)) +# mocker.patch.object(subtensor, "get_subnet_burn_cost", return_value=Balance(10)) +# +# mock_substrate.submit_extrinsic.return_value = mocker.Mock( +# is_success=success, +# ) +# +# result = subtensor.register_subnet(fake_wallet) +# +# assert result.success is success +# +# assert_submit_signed_extrinsic( +# substrate=mock_substrate, +# keypair=fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="register_network", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# }, +# ) +# +# +# def test_register_subnet_insufficient_funds( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mocker.patch.object(subtensor, "get_balance", return_value=Balance(0)) +# mocker.patch.object(subtensor, "get_subnet_burn_cost", return_value=Balance(10)) +# +# success, _ = subtensor.register_subnet(fake_wallet) +# +# assert success is False +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_root_register(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object( +# subtensor, "get_balance", autospec=True, return_value=Balance(100) +# ) +# mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) +# mocker.patch.object( +# subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=False +# ) +# +# success, _ = subtensor.root_register(fake_wallet) +# +# assert success is True +# +# subtensor.get_balance.assert_called_once_with( +# fake_wallet.coldkeypub.ss58_address, +# block=mock_substrate.get_block_number.return_value, +# ) +# subtensor.get_hyperparameter.assert_called_once() +# subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# 0, +# None, +# ) +# +# assert_submit_signed_extrinsic( +# substrate=mock_substrate, +# keypair=fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="root_register", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# }, +# ) +# +# +# def test_root_register_is_already_registered( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mocker.patch.object( +# subtensor, "get_balance", autospec=True, return_value=Balance(100) +# ) +# mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) +# mocker.patch.object( +# subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=True +# ) +# +# success, _ = subtensor.root_register(fake_wallet) +# +# assert success is True +# +# subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# 0, +# None, +# ) +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_sign_and_send_extrinsic(mock_substrate, subtensor, fake_wallet, mocker): +# call = mocker.Mock() +# +# subtensor.sign_and_send_extrinsic( +# call=call, +# wallet=fake_wallet, +# use_nonce=True, +# period=10, +# ) +# +# mock_substrate.get_account_next_index.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# ) +# mock_substrate.create_signed_extrinsic.assert_called_once_with( +# call=call, +# era={ +# "period": 10, +# }, +# keypair=fake_wallet.coldkey, +# nonce=mock_substrate.get_account_next_index.return_value, +# ) +# mock_substrate.submit_extrinsic.assert_called_once_with( +# mock_substrate.create_signed_extrinsic.return_value, +# wait_for_inclusion=True, +# wait_for_finalization=False, +# ) +# +# +# def test_sign_and_send_extrinsic_raises_error( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mock_substrate.submit_extrinsic.return_value = mocker.Mock( +# error_message={ +# "name": "Exception", +# }, +# is_success=False, +# ) +# +# with pytest.raises( +# async_substrate_interface.errors.SubstrateRequestException, +# match="{'name': 'Exception'}", +# ): +# subtensor.sign_and_send_extrinsic( +# call=mocker.Mock(), +# wallet=fake_wallet, +# raise_error=True, +# ) +# +# +# def test_transfer_stake(subtensor, mocker): +# # Preps +# wallet = mocker.Mock() +# destination_coldkey_ss58 = mocker.Mock() +# hotkey_ss58=mocker.Mock() +# origin_netuid=mocker.Mock() +# destination_netuid=mocker.Mock() +# amount = mocker.Mock() +# +# mocked_transfer_stake_extrinsic = mocker.patch.object( +# subtensor_module, "transfer_stake_extrinsic" +# ) +# +# # Call +# response = subtensor.transfer_stake( +# wallet=wallet, +# destination_coldkey_ss58=destination_coldkey_ss58, +# hotkey_ss58=hotkey_ss58, +# origin_netuid=origin_netuid, +# destination_netuid=destination_netuid, +# amount=amount, +# ) +# +# # Asserts +# assert response == mocked_transfer_stake_extrinsic.return_value +# +# mocked_transfer_stake_extrinsic.assert_called_once_with( +# subtensor=subtensor, +# wallet=wallet, +# destination_coldkey_ss58=destination_coldkey_ss58, +# hotkey_ss58=hotkey_ss58, +# origin_netuid=origin_netuid, +# destination_netuid=destination_netuid, +# amount=amount, +# period=None, +# raise_error=False, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_transfer_stake_insufficient_stake( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mocker.patch.object( +# subtensor, +# "get_hotkey_owner", +# autospec=True, +# return_value=fake_wallet.coldkeypub.ss58_address, +# ) +# +# with unittest.mock.patch.object( +# subtensor, +# "get_stake", +# return_value=Balance(0), +# ): +# success, message = subtensor.transfer_stake( +# fake_wallet, +# "dest", +# "hotkey_ss58", +# origin_netuid=1, +# destination_netuid=1, +# amount=Balance(1), +# ) +# +# assert success is False +# assert "Insufficient stake in origin hotkey" in message +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_wait_for_block(mock_substrate, subtensor, mocker): +# mock_subscription_handler = None +# +# def get_block_handler( +# current_block_hash, +# header_only, +# subscription_handler, +# ): +# nonlocal mock_subscription_handler +# mock_subscription_handler = mocker.Mock(wraps=subscription_handler) +# +# for block in range(1, 20): +# if mock_subscription_handler( +# { +# "header": { +# "number": block, +# }, +# } +# ): +# return +# +# assert False +# +# mock_substrate.get_block.side_effect = [ +# { +# "header": { +# "number": 1, +# }, +# }, +# ] +# mock_substrate.get_block_handler.side_effect = get_block_handler +# +# subtensor.wait_for_block(block=9) +# +# assert mock_subscription_handler.call_count == 9 +# +# +# def test_weights(mock_substrate, subtensor): +# mock_substrate.query_map.return_value = [ +# (1, unittest.mock.Mock(value=0.5)), +# ] +# +# results = subtensor.weights( +# netuid=1, +# ) +# +# assert results == [ +# ( +# 1, +# 0.5, +# ), +# ] +# +# mock_substrate.query_map.assert_called_once_with( +# module="SubtensorModule", +# storage_function="Weights", +# params=[1], +# block_hash=None, +# ) From c1c7371f5e1b4dbca28602ac7dc1561a6aee6ce3 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:16:52 -0700 Subject: [PATCH 06/23] remove old files --- bittensor/core/extrinsics/asyncex/mechanism.py | 0 bittensor/core/extrinsics/mechanism.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bittensor/core/extrinsics/asyncex/mechanism.py delete mode 100644 bittensor/core/extrinsics/mechanism.py diff --git a/bittensor/core/extrinsics/asyncex/mechanism.py b/bittensor/core/extrinsics/asyncex/mechanism.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/core/extrinsics/mechanism.py b/bittensor/core/extrinsics/mechanism.py deleted file mode 100644 index e69de29bb2..0000000000 From 04b28182ad629f7a1bad9c617721d920b440e13b Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:14 -0700 Subject: [PATCH 07/23] update readme and license --- LICENSE | 3 ++- README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 8d10866d56..6785ae26c3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ The MIT License (MIT) -Copyright © 2021 Yuma Rao +Copyright © 2025 The Opentensor Foundation +Copyright © 2025 Yuma Rao 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 diff --git a/README.md b/README.md index e74a7e1490..8e117d2d65 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,8 @@ Ready to contribute? Read the [contributing guide](./contrib/CONTRIBUTING.md) be ## License The MIT License (MIT) -Copyright © 2024 The Opentensor Foundation +Copyright © 2025 The Opentensor Foundation +Copyright © 2025 Yuma Rao 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: From ed1ffc2673902e9e75abf9dbbe70140f0d02df9e Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:28 -0700 Subject: [PATCH 08/23] improve SubtensorApi --- bittensor/core/subtensor_api/subnets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index c0e9e6d751..30c31ff7e1 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -44,6 +44,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.max_weight_limit = subtensor.max_weight_limit self.min_allowed_weights = subtensor.min_allowed_weights self.recycle = subtensor.recycle + self.register = subtensor.register self.register_subnet = subtensor.register_subnet self.set_subnet_identity = subtensor.set_subnet_identity self.subnet = subtensor.subnet From 7699f66dcefbd49a8fe235e1f5173b8360bbb301 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:42 -0700 Subject: [PATCH 09/23] update subtensor classes --- bittensor/core/async_subtensor.py | 197 ++++++++++++++++-------------- bittensor/core/subtensor.py | 172 +++++++++++++------------- 2 files changed, 190 insertions(+), 179 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index bbb182abfb..ab5a764781 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -71,16 +71,14 @@ add_stake_multiple_extrinsic, ) from bittensor.core.extrinsics.asyncex.start_call import start_call_extrinsic -from bittensor.core.extrinsics.asyncex.take import ( - decrease_take_extrinsic, - increase_take_extrinsic, -) +from bittensor.core.extrinsics.asyncex.take import set_take_extrinsic from bittensor.core.extrinsics.asyncex.transfer import transfer_extrinsic from bittensor.core.extrinsics.asyncex.unstaking import ( unstake_all_extrinsic, unstake_extrinsic, unstake_multiple_extrinsic, ) +from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee from bittensor.core.extrinsics.asyncex.weights import ( commit_timelocked_weights_extrinsic, commit_weights_extrinsic, @@ -101,11 +99,12 @@ Certificate, decode_hex_identity_dict, format_error_message, + get_caller_name, + get_transfer_fn_params, + get_mechid_storage_index, is_valid_ss58_address, u16_normalized_float, u64_normalized_float, - get_transfer_fn_params, - get_mechid_storage_index, ) from bittensor.utils.balance import ( Balance, @@ -1280,7 +1279,7 @@ async def get_block_hash(self, block: Optional[int] = None) -> str: async def get_parents( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -1290,7 +1289,7 @@ async def get_parents( storage function to get the children and formats them before returning as a tuple. Parameters: - hotkey: The child hotkey SS58. + hotkey_ss58: The child hotkey SS58. netuid: The netuid value. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. @@ -1303,7 +1302,7 @@ async def get_parents( parents = await self.substrate.query( module="SubtensorModule", storage_function="ParentKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -1320,7 +1319,7 @@ async def get_parents( async def get_children( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -1333,7 +1332,7 @@ async def get_children( distribution. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. @@ -1356,7 +1355,7 @@ async def get_children( children = await self.substrate.query( module="SubtensorModule", storage_function="ChildKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -1375,7 +1374,7 @@ async def get_children( async def get_children_pending( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -1390,7 +1389,7 @@ async def get_children_pending( approval or in a cooldown period. These are children that have been proposed but not yet finalized. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number for which the children are to be retrieved. block_hash: The hash of the block to retrieve the subnet unique identifiers from. @@ -1404,7 +1403,7 @@ async def get_children_pending( response = await self.substrate.query( module="SubtensorModule", storage_function="PendingChildKeys", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=await self.determine_block_hash( block, block_hash, @@ -2077,7 +2076,7 @@ async def get_netuids_for_hotkey( async def get_neuron_certificate( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -2088,7 +2087,7 @@ async def get_neuron_certificate( subnet (netuid) of the Bittensor network. Parameters: - hotkey: The hotkey to query. + hotkey_ss58: The hotkey to query. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or @@ -2108,7 +2107,7 @@ async def get_neuron_certificate( name="NeuronCertificates", block_hash=block_hash, reuse_block=reuse_block, - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], ), ) try: @@ -2373,14 +2372,14 @@ async def get_neuron_for_pubkey_and_subnet( ) if (uid := getattr(uid_query, "value", None)) is None: return NeuronInfo.get_null_neuron() - else: - return await self.neuron_for_uid( - uid=uid, - netuid=netuid, - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ) + + return await self.neuron_for_uid( + uid=uid, + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) async def get_next_epoch_start_block( self, @@ -2817,8 +2816,8 @@ async def get_stake_for_coldkey_and_hotkey( results = await asyncio.gather( *[ self.query_runtime_api( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=[hotkey_ss58, coldkey_ss58, netuid], block_hash=block_hash, ) @@ -4185,7 +4184,11 @@ async def sign_and_send_extrinsic( Raises: SubstrateRequestException: Substrate request exception. """ - extrinsic_response = ExtrinsicResponse(extrinsic_function=calling_function) + extrinsic_response = ExtrinsicResponse( + extrinsic_function=calling_function + if calling_function + else get_caller_name() + ) possible_keys = ("coldkey", "hotkey", "coldkeypub") if sign_with not in possible_keys: raise AttributeError( @@ -4205,6 +4208,9 @@ async def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} + extrinsic_response.extrinsic_fee = await get_extrinsic_fee( + subtensor=self, call=call, keypair=signing_keypair + ) extrinsic_response.extrinsic = await self.substrate.create_signed_extrinsic( **extrinsic_data ) @@ -4219,6 +4225,7 @@ async def sign_and_send_extrinsic( extrinsic_response.message = ( "Not waiting for finalization or inclusion." ) + logging.debug(extrinsic_response.message) return extrinsic_response if await response.is_success: @@ -4229,6 +4236,7 @@ async def sign_and_send_extrinsic( if raise_error: raise ChainError.from_error(response_error_message) + extrinsic_response.success = False extrinsic_response.message = format_error_message(response_error_message) extrinsic_response.error = response_error_message @@ -4358,8 +4366,8 @@ async def add_liquidity( async def add_stake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -4372,8 +4380,8 @@ async def add_stake_multiple( Parameters: wallet: The wallet used for staking. - hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. netuids: List of subnet UIDs. + hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You @@ -4492,11 +4500,9 @@ async def commit_weights( See also: , """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to commit weights!" - ) + response = ExtrinsicResponse(False) - logging.info( + logging.debug( f"Committing weights with params: " f"netuid=[blue]{netuid}[/blue], uids=[blue]{uids}[/blue], weights=[blue]{weights}[/blue], " f"version_key=[blue]{version_key}[/blue]" @@ -4518,10 +4524,16 @@ async def commit_weights( raise_error=raise_error, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error committing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug( + "No one successful attempt made. " + "Perhaps it is too soon to commit weights!" + ) return response async def modify_liquidity( @@ -4834,9 +4846,7 @@ async def reveal_weights( See also: , """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to reveal weights!" - ) + response = ExtrinsicResponse(False) while retries < max_retries and response.success is False: try: @@ -4855,10 +4865,13 @@ async def reveal_weights( wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error revealing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug("No attempt made. Perhaps it is too soon to reveal weights!") return response async def root_register( @@ -4933,7 +4946,7 @@ async def root_set_pending_childkey_cooldown( async def set_children( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -4946,7 +4959,7 @@ async def set_children( Parameters: wallet: bittensor wallet instance. - hotkey: The `SS58` address of the neuron's hotkey. + hotkey_ss58: The `SS58` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's @@ -4975,7 +4988,7 @@ async def set_children( return await set_children_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=children, period=period, @@ -5032,23 +5045,18 @@ async def set_delegate_take( current_take_u16 = int(current_take * 0xFFFF) if current_take_u16 == take_u16: - message = f"The take for {hotkey_ss58} is already set to {take}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green].") + message = f"The take for {hotkey_ss58} is already set to {take}." + logging.debug(f"[green]{message}[/green].") return ExtrinsicResponse(True, message) - logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - - extrinsic_call = ( - increase_take_extrinsic - if current_take_u16 < take_u16 - else decrease_take_extrinsic - ) + logging.debug(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - response = await extrinsic_call( + response = await set_take_extrinsic( subtensor=self, wallet=wallet, hotkey_ss58=hotkey_ss58, take=take_u16, + action="increase_take" if current_take_u16 < take_u16 else "decrease_take", period=period, raise_error=raise_error, wait_for_finalization=wait_for_finalization, @@ -5056,8 +5064,9 @@ async def set_delegate_take( ) if response.success: - logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") + return response + logging.error(f"[red]{response.message}[/red]") return response async def set_subnet_identity( @@ -5166,9 +5175,8 @@ async def _blocks_weight_limit() -> bool: return bslu > wrl retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to set weights!" - ) + response = ExtrinsicResponse(False) + if ( uid := await self.get_uid_for_hotkey_on_subnet( wallet.hotkey.ss58_address, netuid @@ -5176,7 +5184,7 @@ async def _blocks_weight_limit() -> bool: ) is None: return ExtrinsicResponse( False, - f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}", + f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}.", ) if await self.commit_reveal_enabled(netuid=netuid): @@ -5187,27 +5195,31 @@ async def _blocks_weight_limit() -> bool: and response.success is False and await _blocks_weight_limit() ): - logging.info( + logging.debug( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - response = await commit_timelocked_weights_extrinsic( - subtensor=self, - wallet=wallet, - netuid=netuid, - mechid=mechid, - uids=uids, - weights=weights, - block_time=block_time, - commit_reveal_version=commit_reveal_version, - version_key=version_key, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + try: + response = await commit_timelocked_weights_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + block_time=block_time, + commit_reveal_version=commit_reveal_version, + version_key=version_key, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + except Exception as error: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - return response else: # go with `set_mechanism_weights_extrinsic` @@ -5217,7 +5229,7 @@ async def _blocks_weight_limit() -> bool: and await _blocks_weight_limit() ): try: - logging.info( + logging.debug( f"Setting weights for subnet #[blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) @@ -5235,11 +5247,16 @@ async def _blocks_weight_limit() -> bool: wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error setting weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - return response + if not response.success: + logging.debug( + "No one successful attempt made. Perhaps it is too soon to set weights!" + ) + return response async def serve_axon( self, @@ -5368,15 +5385,15 @@ async def set_reveal_commitment( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Note: + A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Successful extrinsic's the "data" field contains {"encrypted": encrypted, "reveal_round": reveal_round}. """ encrypted, reveal_round = get_encrypted_commitment( data, blocks_until_reveal, block_time ) - # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed - # and stored. data_ = {"encrypted": encrypted, "reveal_round": reveal_round} response = await publish_metadata_extrinsic( subtensor=self, @@ -5389,7 +5406,7 @@ async def set_reveal_commitment( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - response.data = {"reveal_round": reveal_round} + response.data = data_ return response async def start_call( @@ -5689,7 +5706,7 @@ async def unstake( async def unstake_all( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -5701,7 +5718,7 @@ async def unstake_all( Parameters: wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. @@ -5754,14 +5771,10 @@ async def unstake_all( ) print(result) """ - if netuid != 0: - logging.debug( - f"Unstaking without Alpha price control from subnet [blue]#{netuid}[/blue]." - ) return await unstake_all_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, rate_tolerance=rate_tolerance, period=period, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 700e944033..b251d4aeaf 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -70,16 +70,14 @@ add_stake_multiple_extrinsic, ) from bittensor.core.extrinsics.start_call import start_call_extrinsic -from bittensor.core.extrinsics.take import ( - decrease_take_extrinsic, - increase_take_extrinsic, -) +from bittensor.core.extrinsics.take import set_take_extrinsic from bittensor.core.extrinsics.transfer import transfer_extrinsic from bittensor.core.extrinsics.unstaking import ( unstake_all_extrinsic, unstake_extrinsic, unstake_multiple_extrinsic, ) +from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.core.extrinsics.weights import ( commit_timelocked_weights_extrinsic, commit_weights_extrinsic, @@ -104,11 +102,12 @@ Certificate, decode_hex_identity_dict, format_error_message, + get_caller_name, + get_transfer_fn_params, + get_mechid_storage_index, is_valid_ss58_address, u16_normalized_float, u64_normalized_float, - get_transfer_fn_params, - get_mechid_storage_index, ) from bittensor.utils.balance import ( Balance, @@ -798,14 +797,14 @@ def get_hyperparameter( return getattr(result, "value", result) def get_parents( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> list[tuple[float, str]]: """ This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys storage function to get the children and formats them before returning as a tuple. Parameters: - hotkey: The child hotkey SS58. + hotkey_ss58: The child hotkey SS58. netuid: The netuid. block: The block number for which the children are to be retrieved. @@ -815,7 +814,7 @@ def get_parents( parents = self.substrate.query( module="SubtensorModule", storage_function="ParentKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=self.determine_block_hash(block), ) if parents: @@ -830,14 +829,14 @@ def get_parents( return [] def get_children( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> tuple[bool, list[tuple[float, str]], str]: """ This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys storage function to get the children and formats them before returning as a tuple. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number for which the children are to be retrieved. @@ -849,7 +848,7 @@ def get_children( children = self.substrate.query( module="SubtensorModule", storage_function="ChildKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=self.determine_block_hash(block), ) if children: @@ -867,7 +866,7 @@ def get_children( def get_children_pending( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, ) -> tuple[ @@ -879,7 +878,7 @@ def get_children_pending( It queries the SubtensorModule's PendingChildKeys storage function. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number for which the children are to be retrieved. @@ -891,7 +890,7 @@ def get_children_pending( children, cooldown = self.substrate.query( module="SubtensorModule", storage_function="PendingChildKeys", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=self.determine_block_hash(block), ).value @@ -1401,14 +1400,14 @@ def get_netuids_for_hotkey( return netuids def get_neuron_certificate( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> Optional[Certificate]: """ Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified subnet (netuid) of the Bittensor network. Parameters: - hotkey: The hotkey to query. + hotkey_ss58: The hotkey to query. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -1421,7 +1420,7 @@ def get_neuron_certificate( module="SubtensorModule", name="NeuronCertificates", block=block, - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], ) try: if certificate_query: @@ -1650,27 +1649,21 @@ def get_neuron_for_pubkey_and_subnet( attributes within a particular subnet of the Bittensor ecosystem. """ block_hash = self.determine_block_hash(block) - uid = self.substrate.query( + uid_query = self.substrate.query( module="SubtensorModule", storage_function="Uids", params=[netuid, hotkey_ss58], block_hash=block_hash, ) - if uid is None: + if (uid := getattr(uid_query, "value", None)) is None: return NeuronInfo.get_null_neuron() - result = self.query_runtime_api( - runtime_api="NeuronInfoRuntimeApi", - method="get_neuron", - params=[netuid, uid.value], + return self.neuron_for_uid( + uid=uid, + netuid=netuid, block=block, ) - if not result: - return NeuronInfo.get_null_neuron() - - return NeuronInfo.from_dict(result) - def get_next_epoch_start_block( self, netuid: int, block: Optional[int] = None ) -> Optional[int]: @@ -2036,8 +2029,8 @@ def get_stake_for_coldkey_and_hotkey( all_netuids = netuids results = [ self.query_runtime_api( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=[hotkey_ss58, coldkey_ss58, netuid], block=block, ) @@ -2055,11 +2048,11 @@ def get_stake_for_coldkey( Retrieves the stake information for a given coldkey. Parameters: - coldkey_ss58 The SS58 address of the coldkey. + coldkey_ss58: The SS58 address of the coldkey. block: The block number at which to query the stake information. Returns: - OA list of StakeInfo objects, or ``None`` if no stake information is found. + An optional list of StakeInfo objects, or ``None`` if no stake information is found. """ result = self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", @@ -3081,7 +3074,11 @@ def sign_and_send_extrinsic( Raises: SubstrateRequestException: Substrate request exception. """ - extrinsic_response = ExtrinsicResponse(extrinsic_function=calling_function) + extrinsic_response = ExtrinsicResponse( + extrinsic_function=calling_function + if calling_function + else get_caller_name() + ) possible_keys = ("coldkey", "hotkey", "coldkeypub") if sign_with not in possible_keys: raise AttributeError( @@ -3102,6 +3099,9 @@ def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} + extrinsic_response.extrinsic_fee = get_extrinsic_fee( + subtensor=self, call=call, keypair=signing_keypair + ) extrinsic_response.extrinsic = self.substrate.create_signed_extrinsic( **extrinsic_data ) @@ -3116,6 +3116,7 @@ def sign_and_send_extrinsic( extrinsic_response.message = ( "Not waiting for finalization or inclusion." ) + logging.debug(extrinsic_response.message) return extrinsic_response if response.is_success: @@ -3256,8 +3257,8 @@ def add_liquidity( def add_stake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -3270,8 +3271,8 @@ def add_stake_multiple( Parameters: wallet: The wallet used for staking. - hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. netuids: List of subnet UIDs. + hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You @@ -3387,11 +3388,9 @@ def commit_weights( time, enhancing transparency and accountability within the Bittensor network. """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to commit weights!" - ) + response = ExtrinsicResponse(False) - logging.info( + logging.debug( f"Committing weights with params: " f"netuid=[blue]{netuid}[/blue], uids=[blue]{uids}[/blue], weights=[blue]{weights}[/blue], " f"version_key=[blue]{version_key}[/blue]" @@ -3412,13 +3411,17 @@ def commit_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - if response.success: - break except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error committing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug( + "No one successful attempt made. " + "Perhaps it is too soon to commit weights!" + ) return response def modify_liquidity( @@ -3731,9 +3734,7 @@ def reveal_weights( See also: , """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to reveal weights!" - ) + response = ExtrinsicResponse(False) while retries < max_retries and response.success is False: try: @@ -3752,10 +3753,13 @@ def reveal_weights( wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error revealing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug("No attempt made. Perhaps it is too soon to reveal weights!") return response def root_register( @@ -3830,7 +3834,7 @@ def root_set_pending_childkey_cooldown( def set_children( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -3843,7 +3847,7 @@ def set_children( Parameters: wallet: bittensor wallet instance. - hotkey: The ``SS58`` address of the neuron's hotkey. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's @@ -3859,7 +3863,7 @@ def set_children( return set_children_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=children, period=period, @@ -3916,23 +3920,18 @@ def set_delegate_take( current_take_u16 = int(current_take * 0xFFFF) if current_take_u16 == take_u16: - message = f"The take for {hotkey_ss58} is already set to {take}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green].") + message = f"The take for {hotkey_ss58} is already set to {take}." + logging.debug(f"[green]{message}[/green].") return ExtrinsicResponse(True, message) - logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - - extrinsic_call = ( - increase_take_extrinsic - if current_take_u16 < take_u16 - else decrease_take_extrinsic - ) + logging.debug(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - response = extrinsic_call( + response = set_take_extrinsic( subtensor=self, wallet=wallet, hotkey_ss58=hotkey_ss58, take=take_u16, + action="increase_take" if current_take_u16 < take_u16 else "decrease_take", period=period, raise_error=raise_error, wait_for_finalization=wait_for_finalization, @@ -3940,8 +3939,9 @@ def set_delegate_take( ) if response.success: - logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") + return response + logging.error(f"[red]{response.message}[/red]") return response def set_subnet_identity( @@ -4045,15 +4045,14 @@ def _blocks_weight_limit() -> bool: return bslu > wrl retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to set weights!" - ) + response = ExtrinsicResponse(False) + if ( uid := self.get_uid_for_hotkey_on_subnet(wallet.hotkey.ss58_address, netuid) ) is None: return ExtrinsicResponse( False, - f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}", + f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}.", ) if self.commit_reveal_enabled(netuid=netuid): @@ -4064,7 +4063,7 @@ def _blocks_weight_limit() -> bool: and response.success is False and _blocks_weight_limit() ): - logging.info( + logging.debug( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) @@ -4084,13 +4083,11 @@ def _blocks_weight_limit() -> bool: wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error setting weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - - return response else: # go with `set_mechanism_weights_extrinsic` @@ -4100,7 +4097,7 @@ def _blocks_weight_limit() -> bool: and _blocks_weight_limit() ): try: - logging.info( + logging.debug( f"Setting weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) @@ -4118,11 +4115,16 @@ def _blocks_weight_limit() -> bool: wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error setting weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - return response + if not response.success: + logging.debug( + "No one successful attempt made. Perhaps it is too soon to set weights!" + ) + return response def serve_axon( self, @@ -4252,15 +4254,15 @@ def set_reveal_commitment( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Note: + A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Successful extrinsic's the "data" field contains {"encrypted": encrypted, "reveal_round": reveal_round}. """ encrypted, reveal_round = get_encrypted_commitment( data, blocks_until_reveal, block_time ) - # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed - # and stored. data_ = {"encrypted": encrypted, "reveal_round": reveal_round} response = publish_metadata_extrinsic( subtensor=self, @@ -4574,7 +4576,7 @@ def unstake( def unstake_all( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -4586,7 +4588,7 @@ def unstake_all( Parameters: wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. @@ -4638,14 +4640,10 @@ def unstake_all( ) print(result) """ - if netuid != 0: - logging.debug( - f"Unstaking without Alpha price control from subnet [blue]#{netuid}[/blue]." - ) return unstake_all_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, rate_tolerance=rate_tolerance, period=period, From a3c515223540b3b6ad5a10cbb50c688b66245989 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:53 -0700 Subject: [PATCH 10/23] pyproject.toml --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 28b4cd1de2..6bc44e8d40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor" -version = "9.11.1" -description = "Bittensor" +version = "10.0.0rc" +description = "Bittensor SDK" readme = "README.md" authors = [ {name = "bittensor.com"} From 33058e461b316da50d07de7b7b1eb152ce46fb47 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:18:02 -0700 Subject: [PATCH 11/23] update MIGRATION.md --- MIGRATION.md | 53 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index dd27b8f1e7..128f70ed28 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -13,7 +13,7 @@ 3. ✅ Set `wait_for_inclusion` and `wait_for_finalization` to `True` by default in extrinsics and their related calls. Then we will guarantee the correct/expected extrinsic call response is consistent with the chain response. If the user changes those values, then it is the user's responsibility. 4. ✅ Make the internal logic of extrinsics the same. There are extrinsics that are slightly different in implementation. -5. Since SDK is not a responsible tool, try to remove all calculations inside extrinsics that do not affect the result, but are only used in logging. Actually, this should be applied not to extrinsics only but for all codebase. +5. ✅ ~~Since SDK is not a responsible tool, try to remove all calculations inside extrinsics that do not affect the result, but are only used in logging. Actually, this should be applied not to extrinsics only but for all codebase.~~ Just improved regarding usage. 6. ✅ Remove `unstake_all` parameter from `unstake_extrinsic` since we have `unstake_all_extrinsic`which is calles another subtensor function. @@ -21,7 +21,7 @@ 8. ✅ Remove `_do*` extrinsic calls and combine them with extrinsic logic. -9. `subtensor.get_transfer_fee` calls extrinsic inside the subtensor module. Actually the method could be updated by using `bittensor.core.extrinsics.utils.get_extrinsic_fee`. +9. ✅ ~~`subtensor.get_transfer_fee` calls extrinsic inside the subtensor module. Actually the method could be updated by using `bittensor.core.extrinsics.utils.get_extrinsic_fee`.~~ `get_transfer_fee` isn't `get_extrinsic_fee` ## Subtensor 1. In the synchronous Subtensor class, the `get_owned_hotkeys` method includes a `reuse_block` parameter that is inconsistent with other methods. Either remove this parameter from `get_owned_hotkeys`, or add it to all other methods that directly call self.substrate.* to maintain a consistent interface. @@ -41,7 +41,7 @@ This may seem like a harsh decision at first, but ultimately we will push the community to use Balance and there will be fewer errors in their calculations. Confusion with TAO and Alpha in calculations and display/printing/logging will be eliminated. ## Common things -1. Reduce the amount of logging.info or transfer part of logging.info to logging.debug +1. Reduce the amount of logging.info or transfer part of logging.info to logging.debug `(in progress)` 2. To be consistent across all SDK regarding local environment variables name: remove `BT_CHAIN_ENDPOINT` (settings.py :line 124) and use `BT_SUBTENSOR_CHAIN_ENDPOINT` instead of that. @@ -53,7 +53,7 @@ rename this variable in documentation. 5. Rename `non-/fast-blocks` to `non-/fast-runtime` in related places to be consistent with subtensor repo. Related with testing, subtensor scripts, documentation. -6. To be consistent throughout the SDK: +6. To be consistent throughout the SDK `(in progress)`: `hotkey`, `coldkey`, `hotkeypub`, and `coldkeypub` are keypairs `hotkey_ss58`, `coldkey_ss58`, `hotkeypub_ss58`, and `coldkeypub_ss58` are SS58 addresses of keypair. @@ -61,9 +61,9 @@ rename this variable in documentation. 8. ✅ Remove all type annotations for parameters in docstrings. -9. Remove all logic related to CRv3 as it will be removed from the chain next week. +9. ✅ Remove all logic related to CRv3 as it will be removed from the chain next week. - [x] CRv3 extrinsics - - [ ] CRv3 logic related subtensor's calls + - [x] CRv3 logic related subtensor's calls 10. Revise `bittensor/utils/easy_imports.py` module to remove deprecated backwards compatibility objects. Use this module as a functionality for exporting existing objects to the package root to keep __init__.py minimal and simple. @@ -77,10 +77,10 @@ rename this variable in documentation. ## New features 1. Add `bittensor.utils.hex_to_ss58` function. SDK still doesn't have it. (Probably inner import `from scalecodec import ss58_encode, ss58_decode`) 2. Implement Crowdloan logic. Issue: https://github.com/opentensor/bittensor/issues/3017 -3. Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 +3. ✅ Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 ## Testing -1. When running tests via Docker, ensure no lingering processes occupy required ports before launch. +1. ✅ When running tests via Docker, ensure no lingering processes occupy required ports before launch. 2. Improve failed test reporting from GH Actions to the Docker channel (e.g., clearer messages, formatting). @@ -112,13 +112,13 @@ It must include: # Migration guide -- [x] `._do_commit_reveal_v3` logic is included in the main code `.commit_reveal_v3_extrinsic` -- [x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` +- [x] `._do_commit_reveal_v3` logic is included in the main code `.commit_timelocked_weights_extrinsic` + - [x] `commit_reveal_version` parameter with default value `4` added to `commit_timelocked_weights_extrinsic` - [x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` - [x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` - [x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` - [x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` -- [x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) + - [x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) - [x] `_do_burned_register` logic is included in the main code `.burned_register_extrinsic` - [x] `_do_pow_register` logic is included in the main code `.register_extrinsic` - [x] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` @@ -199,25 +199,38 @@ Additional changes in extrinsics: Removing deprecated extrinsics and replacing them with consistent ones: - `commit_reveal_extrinsic` (without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_timelocked_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_timelocked_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_timelocked_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_timelocked_weights_extrinsic` - `commit_weights_extrinsic`(without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_weights_extrinsic` - `reveal_weights_extrinsic`(without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` - `set_weights_extrinsic`(without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` +- `decrease_take_extrinsic` and `increase_take_extrinsic` have been merged into a single set_take_extrinsic. The API now has a new `action: Literal["increase_take", "decrease_take"]` parameter (DRY rule). + +### Extrinsics has extra data in response's `data` field: +- `add_stake_extrinsic` +- `add_stake_multiple_extrinsic` +- `burned_register_extrinsic` +- `register_extrinsic` +- `transfer_extrinsic` +- `unstake_extrinsic` +- `unstake_multiple_extrinsic` ### Subtensor changes -- method `all_subnets` has renamed parameter from `block_number` to `block`. +- method `all_subnets` has renamed parameter from `block_number` to `block` (consistency in the codebase). +- The `hotkey` parameter, which meant ss58 key address, was renamed to `hotkey_ss58` in all methods (consistency in the codebase). +- The `coldkey` parameter, which meant ss58 key address, was renamed to `coldkey_ss58` in all methods (consistency in the codebase). - method `query_subtensor` has updated parameters order. - method `query_module` has updated parameters order. - method `query_map_subtensor` has updated parameters order. - method `query_map` has updated parameters order. +- method `add_stake_multiple` has updated parameters order. From bce925f47e477635cfbac61a7fceceb369442b6b Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 00:02:48 -0700 Subject: [PATCH 12/23] fix and improve tests + ruff --- bittensor/core/types.py | 5 +- tests/e2e_tests/test_hotkeys.py | 593 ++++++++++----------- tests/e2e_tests/test_neuron_certificate.py | 4 +- 3 files changed, 298 insertions(+), 304 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 94dc91bd20..543ee8eb64 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -486,7 +486,10 @@ def from_exception(cls, raise_error: bool, error: Exception) -> "ExtrinsicRespon ).with_log() def with_log( - self, level: Literal["trace", "debug", "info", "warning", "error", "success"] = "error" + self, + level: Literal[ + "trace", "debug", "info", "warning", "error", "success" + ] = "error", ) -> "ExtrinsicResponse": """Logs provided message with provided level. diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index f5fe16e857..261703f83b 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -193,7 +193,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(RegistrationNotPermittedOnRootSubnet): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=0, children=[], raise_error=True, @@ -202,7 +202,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(NonAssociatedColdKey): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=1, children=[], raise_error=True, @@ -210,8 +210,8 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(SubnetNotExists): subtensor.extrinsics.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=3, children=[], raise_error=True, @@ -230,7 +230,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -241,7 +241,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(InvalidChild): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -255,7 +255,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(TooManyChildren): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -270,7 +270,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(ProportionOverflow): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -288,7 +288,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(DuplicateChild): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -305,7 +305,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): success, message = subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -324,18 +324,17 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): # children not set yet (have to wait cool-down period) success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, block=set_children_block, netuid=dave_subnet_netuid, ) - assert success is True assert children == [] assert error == "" # children are in pending state pending, cooldown = subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -349,10 +348,9 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) - assert error == "" assert success is True assert children == [(1.0, bob_wallet.hotkey.ss58_address)] @@ -365,7 +363,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): # pending queue is empty pending, cooldown = subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) assert pending == [] @@ -377,25 +375,25 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): set_children_block = subtensor.block subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, ) subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, ) # wait for rate limit to expire + 1 block to ensure that the rate limit is expired - subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 1) + subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 5) success, message = subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, @@ -408,7 +406,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): set_children_block = subtensor.block pending, cooldown = subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -417,12 +415,20 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" ) - subtensor.wait_for_block(cooldown + 1) + subtensor.wait_for_block(cooldown) - success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait. But no longer than one tempo. + children = [] + while not children: + success, children, error = subtensor.wallets.get_children( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + logging.console.info(f"block: {subtensor.block}") + if subtensor.block > cooldown + SET_TEMPO: + break + subtensor.wait_for_block() assert error == "" assert success is True @@ -442,7 +448,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(NotEnoughStakeToSetChildkeys): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -455,11 +461,9 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): @pytest.mark.asyncio -async def test_children_async( - async_subtensor, alice_wallet, bob_wallet, dave_wallet, subtensor -): +async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): """ - Async tests: + Tests: - Get default children (empty list) - Call `root_set_pending_childkey_cooldown` extrinsic. - Update children list @@ -468,345 +472,332 @@ async def test_children_async( - Trigger rate limit - Clear children list """ - async with async_subtensor as async_subtensor: - # turn off admin freeze window limit for testing - window_response, is_fast_blocks = await asyncio.gather( - async_sudo.sudo_set_admin_freeze_window_extrinsic( - async_subtensor, alice_wallet, 0 - ), - async_subtensor.chain.is_fast_blocks(), + # turn off admin freeze window limit for testing + assert ( + await async_sudo.sudo_set_admin_freeze_window_extrinsic( + async_subtensor, alice_wallet, 0 ) + ).success - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if await async_subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) - assert window_response.success, window_response.message + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - SET_TEMPO = FAST_RUNTIME_TEMPO if is_fast_blocks else NON_FAST_RUNTIME_TEMPO + # Set cooldown + ( + success, + message, + ) = await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( + wallet=alice_wallet, cooldown=ROOT_COOLDOWN + ) + assert success is True, message + assert message == "Success" - cooldown_response = ( - await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( - wallet=alice_wallet, cooldown=ROOT_COOLDOWN - ) - ) - assert cooldown_response.success, cooldown_response.message + assert await async_subtensor.subnets.register_subnet(dave_wallet) + assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( + f"Subnet #{dave_subnet_netuid} does not exist." + ) - assert await async_subtensor.subnets.register_subnet(dave_wallet) + assert await async_wait_to_start_call( + async_subtensor, dave_wallet, dave_subnet_netuid + ) - subnet_exists, start_call, tempo_call = await asyncio.gather( - async_subtensor.subnets.subnet_exists(dave_subnet_netuid), - async_wait_to_start_call(async_subtensor, dave_wallet, dave_subnet_netuid), - async_sudo.sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, - ), + # set the same tempo for both type of nodes (to avoid tests timeout) + assert ( + await async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, ) - assert subnet_exists, "Subnet does not exist." - assert start_call, "Subnet did not start." + ).success + assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - tempo_call = await async_sudo.sudo_call_extrinsic( + assert ( + await async_sudo.sudo_call_extrinsic( subtensor=async_subtensor, wallet=alice_wallet, call_function="sudo_set_tx_rate_limit", call_params={"tx_rate_limit": 0}, ) + ).success - assert tempo_call.success, tempo_call.message - assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - - with pytest.raises(RegistrationNotPermittedOnRootSubnet): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=0, - children=[], - raise_error=True, - ) - - with pytest.raises(NonAssociatedColdKey): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=1, - children=[], - raise_error=True, - ) - - with pytest.raises(SubnetNotExists): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=3, - children=[], - raise_error=True, - ) - - # we can't do more than one registration within one block (avoid gather) - # https://docs.learnbittensor.org/errors/subtensor#toomanyregistrationsthisblock - alice_register_call = await async_subtensor.subnets.burned_register( + with pytest.raises(RegistrationNotPermittedOnRootSubnet): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=0, + children=[], + raise_error=True, + ) + + with pytest.raises(NonAssociatedColdKey): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=1, + children=[], + raise_error=True, + ) + + with pytest.raises(SubnetNotExists): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=3, + children=[], + raise_error=True, + ) + + assert ( + await async_subtensor.subnets.burned_register( wallet=alice_wallet, netuid=dave_subnet_netuid, ) - await async_subtensor.wait_for_block() + ).success + logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") - bob_register_call, (success, children, error) = await asyncio.gather( - async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ), - async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ), + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, ) + ).success + logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") + + success, children, error = await async_subtensor.wallets.get_children( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + + assert error == "" + assert success is True + assert children == [] - assert alice_register_call.success, alice_register_call.message - logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") - - assert bob_register_call.success, bob_register_call.message - logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") - - assert error == "" - assert success is True - assert children == [] - - with pytest.raises(InvalidChild): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 1.0, - alice_wallet.hotkey.ss58_address, - ), - ], - raise_error=True, - ) - - with pytest.raises(TooManyChildren): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 0.1, - bob_wallet.hotkey.ss58_address, - ) - for _ in range(10) - ], - raise_error=True, - ) - - with pytest.raises(ProportionOverflow): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 1.0, - bob_wallet.hotkey.ss58_address, - ), - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ], - raise_error=True, - ) - - with pytest.raises(DuplicateChild): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 0.5, - bob_wallet.hotkey.ss58_address, - ), - ( - 0.5, - bob_wallet.hotkey.ss58_address, - ), - ], - raise_error=True, - ) - - success, message = await async_subtensor.extrinsics.set_children( + with pytest.raises(InvalidChild): + await async_subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( 1.0, - bob_wallet.hotkey.ss58_address, + alice_wallet.hotkey.ss58_address, ), ], raise_error=True, - wait_for_inclusion=True, - wait_for_finalization=True, ) - assert success, message - assert message == "Success" - - set_children_block = await async_subtensor.chain.get_current_block() - # children not set yet (have to wait cool-down period) - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - block=set_children_block, + with pytest.raises(TooManyChildren): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, + children=[ + ( + 0.1, + bob_wallet.hotkey.ss58_address, + ) + for _ in range(10) + ], + raise_error=True, ) - assert success is True - assert children == [] - assert error == "" - - # children are in pending state - pending, cooldown = await async_subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, + with pytest.raises(ProportionOverflow): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ], + raise_error=True, ) - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + with pytest.raises(DuplicateChild): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 0.5, + bob_wallet.hotkey.ss58_address, + ), + ( + 0.5, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, ) - assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] + success, message = await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success is True, message + assert message == "Success" - # we use `*2` to ensure that the chain has time to process - await async_subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) + set_children_block = await async_subtensor.block - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + # children not set yet (have to wait cool-down period) + success, children, error = await async_subtensor.wallets.get_children( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + block=set_children_block, + netuid=dave_subnet_netuid, + ) + assert success is True + assert children == [] + assert error == "" - # we need to wait some amount of blocks to ensure that children are posted on chain - # than slower the machine then longer need to wait - while not children: - await async_subtensor.wait_for_block(cooldown) - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) - logging.console.info(f"block: {await async_subtensor.block}") + # children are in pending state + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - assert error == "" - assert success is True - assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) - parent_ = await async_subtensor.wallets.get_parents( - bob_wallet.hotkey.ss58_address, dave_subnet_netuid - ) + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] - assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] + # we use `*2` to ensure that the chain has time to process + await async_subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) - # pending queue is empty - pending, cooldown = await async_subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) - assert pending == [] - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" - ) + success, children, error = await async_subtensor.wallets.get_children( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + assert error == "" + assert success is True + assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + + parent_ = await async_subtensor.wallets.get_parents( + bob_wallet.hotkey.ss58_address, dave_subnet_netuid + ) + + assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] + + # pending queue is empty + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) with pytest.raises(TxRateLimitExceeded): set_children_block = await async_subtensor.chain.get_current_block() await async_subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, + wait_for_finalization=False, ) await async_subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, + wait_for_finalization=False, ) - await async_subtensor.wait_for_block( - set_children_block + SET_CHILDREN_RATE_LIMIT + 1 - ) + await async_subtensor.wait_for_block( + set_children_block + SET_CHILDREN_RATE_LIMIT + 1 + ) - success, message = await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[], - raise_error=True, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert success is True, message - assert message == "Success" + success, message = await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[], + raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success is True, message + assert message == "Success" - set_children_block = await async_subtensor.chain.get_current_block() + set_children_block = await async_subtensor.block - pending, cooldown = await async_subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - assert pending == [] - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" - ) + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) - await async_subtensor.wait_for_block(cooldown + 1) + await async_subtensor.wait_for_block(cooldown) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait. But no longer than one tempo. + children = [] + while not children: success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) + logging.console.info(f"block: {await async_subtensor.block}") + if await async_subtensor.block > cooldown + SET_TEMPO: + break + await async_subtensor.wait_for_block() - # we need to wait some amount of blocks to ensure that children are posted on chain - # than slower the machine then longer need to wait - while not children: - await async_subtensor.wait_for_block(cooldown) - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) - logging.console.info(f"block: {await async_subtensor.block}") - - assert error == "" - assert success is True - assert children == [(1.0, bob_wallet.hotkey.ss58_address)] - - await async_subtensor.wait_for_block( - set_children_block + SET_CHILDREN_RATE_LIMIT - ) - - assert ( - await async_sudo.sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_stake_threshold", - call_params={ - "min_stake": 1_000_000_000_000, - }, - ) - ).success - - with pytest.raises(NotEnoughStakeToSetChildkeys): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - children=[ - ( - 1.0, - bob_wallet.hotkey.ss58_address, - ), - ], - raise_error=True, - ) + assert error == "" + assert success is True + assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + + await async_subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + + await async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_stake_threshold", + call_params={ + "min_stake": 1_000_000_000_000, + }, + ) + + with pytest.raises(NotEnoughStakeToSetChildkeys): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index d4c80a286b..198e581ad0 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -44,7 +44,7 @@ async def test_neuron_certificate(subtensor, alice_wallet): assert ( subtensor.neurons.get_neuron_certificate( netuid=netuid, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, ) == encoded_certificate ) @@ -96,7 +96,7 @@ async def test_neuron_certificate_async(async_subtensor, alice_wallet): assert ( await async_subtensor.neurons.get_neuron_certificate( netuid=netuid, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, ) == encoded_certificate ) From 9e21ef86e8ef5e5a38dc693e1e3af3b3606a8158 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 08:10:11 -0700 Subject: [PATCH 13/23] try faster runners --- .github/workflows/_run-e2e-single.yaml | 2 +- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 29b09650da..57b832d9f7 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -13,7 +13,7 @@ on: jobs: run-e2e: name: "${{ matrix.python-version }}" - runs-on: ubuntu-latest + runs-on: [self-hosted, type-ccx33] timeout-minutes: 45 strategy: diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index dc9d20dba8..dd2608fe12 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -21,7 +21,7 @@ on: jobs: # Looking for e2e tests find-tests: - runs-on: ubuntu-latest + runs-on: [self-hosted, type-ccx33] if: ${{ github.event.pull_request.draft == false }} outputs: test-files: ${{ steps.get-tests.outputs.test-files }} From 8ca75afb0344249a8bc3d411c44d1edcc39fbc2b Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 08:16:41 -0700 Subject: [PATCH 14/23] nope --- .github/workflows/_run-e2e-single.yaml | 2 +- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 57b832d9f7..29b09650da 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -13,7 +13,7 @@ on: jobs: run-e2e: name: "${{ matrix.python-version }}" - runs-on: [self-hosted, type-ccx33] + runs-on: ubuntu-latest timeout-minutes: 45 strategy: diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index dd2608fe12..dc9d20dba8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -21,7 +21,7 @@ on: jobs: # Looking for e2e tests find-tests: - runs-on: [self-hosted, type-ccx33] + runs-on: ubuntu-latest if: ${{ github.event.pull_request.draft == false }} outputs: test-files: ${{ steps.get-tests.outputs.test-files }} From 2de2e9008dc526fd92be3994a327fcf2dd8514fd Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 09:05:57 -0700 Subject: [PATCH 15/23] more time to re-run docker --- .github/workflows/_run-e2e-single.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 29b09650da..06eca8cc12 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -74,7 +74,7 @@ jobs: echo "::endgroup::" if [ "$i" -lt 3 ]; then echo "Retrying..." - sleep 5 + sleep 15 fi fi From 63c32c4c632e6d3aa631c635adf425a43d24d6e3 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 09:06:16 -0700 Subject: [PATCH 16/23] fix if logic for fast block test --- tests/e2e_tests/test_staking.py | 3352 ++++++++++++++++--------------- 1 file changed, 1677 insertions(+), 1675 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 790e176444..fe30c628ac 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,636 +18,636 @@ from tests.helpers.helpers import CloseInValue -def test_single_operation(subtensor, alice_wallet, bob_wallet): - """ - Tests: - - Staking using `add_stake` - - Unstaking using `unstake` - - Checks StakeInfo - """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - # Register root as Alice - the subnet owner and validator - assert subtensor.subnets.register_subnet(alice_wallet).success - - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - assert stake == Balance(0).set_unit(alice_subnet_netuid) - - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ).success - - stake_alice = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - logging.console.info(f"Alice stake: {stake_alice}") - - stake_bob = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - logging.console.info(f"Bob stake: {stake_bob}") - assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) - - stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) - - expected_stakes = [ - StakeInfo( - hotkey_ss58=stakes[0].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - ] - - fast_blocks_stake = ( - [ - StakeInfo( - hotkey_ss58=stakes[1].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance( - stakes[1].emission.rao, alice_subnet_netuid - ), - drain=0, - is_registered=True, - ) - ] - if subtensor.chain.is_fast_blocks() - else [] - ) - - expected_stakes += fast_blocks_stake - - assert stakes == expected_stakes - assert ( - subtensor.staking.get_stake_for_coldkey - == subtensor.staking.get_stake_info_for_coldkey - ) - - stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, - ) - - assert stakes == { - 0: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=0, - stake=Balance(0), - locked=Balance(0), - emission=Balance(0), - drain=0, - is_registered=False, - ), - 1: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=1, - stake=stake.set_unit(1), - locked=Balance.from_tao(0, netuid=1), - emission=Balance.from_tao(0, netuid=1), - drain=0, - is_registered=False, - ), - 2: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), - locked=Balance.from_tao(0, netuid=alice_subnet_netuid), - emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - } - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - logging.console.info(f"Alice stake before unstake: {stake}") - - # unstale all to check in later - response = subtensor.staking.unstake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=stake, - period=16, - ) - - assert response.success is True - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - # all balances have been unstaked - assert stake == Balance(0).set_unit(alice_subnet_netuid) - - -@pytest.mark.asyncio -async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet): - """ - Async ests: - - Staking using `add_stake` - - Unstaking using `unstake` - - Checks StakeInfo - """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - - # Register root as Alice - the subnet owner and validator - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - - # Verify subnet created successfully - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert await async_wait_to_start_call( - async_subtensor, alice_wallet, alice_subnet_netuid - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") - - stake = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - assert stake == Balance(0).set_unit(alice_subnet_netuid) - - assert ( - await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ) - ).success - - stake_alice = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - logging.console.info(f"Alice stake: {stake_alice}") - - stake_bob = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - logging.console.info(f"Bob stake: {stake_bob}") - assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) - - stakes = await async_subtensor.staking.get_stake_for_coldkey( - alice_wallet.coldkey.ss58_address - ) - - expected_stakes = [ - StakeInfo( - hotkey_ss58=stakes[0].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - ] - - fast_blocks_stake = ( - [ - StakeInfo( - hotkey_ss58=stakes[1].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance( - stakes[1].emission.rao, alice_subnet_netuid - ), - drain=0, - is_registered=True, - ) - ] - if await async_subtensor.chain.is_fast_blocks() - else [] - ) - - expected_stakes += fast_blocks_stake - - assert stakes == expected_stakes - assert ( - async_subtensor.staking.get_stake_for_coldkey - == async_subtensor.staking.get_stake_info_for_coldkey - ) - - stakes = await async_subtensor.staking.get_stake_for_coldkey_and_hotkey( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, - ) - - assert stakes == { - 0: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=0, - stake=Balance(0), - locked=Balance(0), - emission=Balance(0), - drain=0, - is_registered=False, - ), - 1: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=1, - stake=stake.set_unit(1), - locked=Balance.from_tao(0, netuid=1), - emission=Balance.from_tao(0, netuid=1), - drain=0, - is_registered=False, - ), - 2: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), - locked=Balance.from_tao(0, netuid=alice_subnet_netuid), - emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - } - - stake = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - logging.console.info(f"Alice stake before unstake: {stake}") - - # unstale all to check in later - success, message = await async_subtensor.staking.unstake_all( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - period=16, - ) - assert success is True, message - - stake = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - logging.console.info(f"Alice stake after unstake: {stake}") - - # all balances have been unstaked - assert stake == Balance(0).set_unit(alice_subnet_netuid) - - -def test_batch_operations(subtensor, alice_wallet, bob_wallet): - """ - Tests: - - Staking using `add_stake_multiple` - - Unstaking using `unstake_multiple` - - Checks StakeInfo - - Checks Accounts Balance - """ - netuids = [ - 2, - 3, - ] - - for _ in netuids: - assert subtensor.subnets.register_subnet(alice_wallet).success - - # make sure we passed start_call limit for both subnets - for netuid in netuids: - assert wait_to_start_call(subtensor, alice_wallet, netuid) - - for netuid in netuids: - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=netuid, - ).success - - for netuid in netuids: - stake = subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - - assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - - balances = subtensor.wallets.get_balances( - alice_wallet.coldkey.ss58_address, - bob_wallet.coldkey.ss58_address, - ) - - expected_balances = { - alice_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[alice_wallet.coldkey.ss58_address].rao - ), - bob_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[bob_wallet.coldkey.ss58_address].rao - ), - } - - assert balances == expected_balances - - alice_balance = balances[alice_wallet.coldkey.ss58_address] - - assert subtensor.staking.add_stake_multiple( - wallet=alice_wallet, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - netuids=netuids, - amounts=[Balance.from_tao(10_000) for _ in netuids], - ).success - - stakes = [ - subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - for netuid in netuids - ] - - for netuid, stake in zip(netuids, stakes): - assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - - alice_balance -= len(netuids) * Balance.from_tao(10_000) - - balances = subtensor.wallets.get_balances( - alice_wallet.coldkey.ss58_address, - bob_wallet.coldkey.ss58_address, - ) - - expected_balances = { - alice_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[alice_wallet.coldkey.ss58_address].rao - ), - bob_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[bob_wallet.coldkey.ss58_address].rao - ), - } - - assert balances == expected_balances - - expected_fee_paid = Balance(0) - for netuid in netuids: - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": bob_wallet.hotkey.ss58_address, - "amount_unstaked": Balance.from_tao(100).rao, - "netuid": netuid, - }, - ) - payment_info = subtensor.substrate.get_payment_info( - call, alice_wallet.coldkeypub - ) - fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) - dynamic_info = subtensor.subnets.subnet(netuid) - fee_tao = dynamic_info.alpha_to_tao(fee_alpha) - expected_fee_paid += fee_tao - - response = subtensor.staking.unstake_multiple( - wallet=alice_wallet, - netuids=netuids, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - amounts=[Balance.from_tao(100) for _ in netuids], - raise_error=True, - ) - logging.console.info(f">>> res {response}") - assert response.success, response.message - - for netuid, old_stake in zip(netuids, stakes): - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - - assert stake < old_stake, f"netuid={netuid} stake={stake}" - - balances = subtensor.wallets.get_balances( - alice_wallet.coldkey.ss58_address, - bob_wallet.coldkey.ss58_address, - ) - - assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees - balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) - ) == Balance.from_tao(999_999.7994) - - assert balances[alice_wallet.coldkey.ss58_address] > alice_balance - - -@pytest.mark.asyncio -async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): - """ - Async tests: - - Staking using `add_stake_multiple` - - Unstaking using `unstake_multiple` - - Checks StakeInfo - - Checks Accounts Balance - """ - netuids = [ - 2, - 3, - ] - - for _ in netuids: - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - - # make sure we passed start_call limit for both subnets - for netuid in netuids: - assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) - - for netuid in netuids: - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=netuid, - ) - ).success - - for netuid in netuids: - stake = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - - assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - - balances = await async_subtensor.wallets.get_balances( - alice_wallet.coldkey.ss58_address, - bob_wallet.coldkey.ss58_address, - ) - - expected_balances = { - alice_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[alice_wallet.coldkey.ss58_address].rao - ), - bob_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[bob_wallet.coldkey.ss58_address].rao - ), - } - - assert balances == expected_balances - - alice_balance = balances[alice_wallet.coldkey.ss58_address] - - response = await async_subtensor.staking.add_stake_multiple( - wallet=alice_wallet, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - netuids=netuids, - amounts=[Balance.from_tao(10_000) for _ in netuids], - ) - assert response.success - - stakes = [ - await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - for netuid in netuids - ] - - for netuid, stake in zip(netuids, stakes): - assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - - alice_balance -= len(netuids) * Balance.from_tao(10_000) - - balances = await async_subtensor.wallets.get_balances( - alice_wallet.coldkey.ss58_address, - bob_wallet.coldkey.ss58_address, - ) - - expected_balances = { - alice_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[alice_wallet.coldkey.ss58_address].rao - ), - bob_wallet.coldkey.ss58_address: get_dynamic_balance( - balances[bob_wallet.coldkey.ss58_address].rao - ), - } - - assert balances == expected_balances - - expected_fee_paid = Balance(0) - for netuid in netuids: - call = await async_subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": bob_wallet.hotkey.ss58_address, - "amount_unstaked": Balance.from_tao(100).rao, - "netuid": netuid, - }, - ) - payment_info = await async_subtensor.substrate.get_payment_info( - call, alice_wallet.coldkeypub - ) - fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) - dynamic_info = await async_subtensor.subnets.subnet(netuid) - fee_tao = dynamic_info.alpha_to_tao(fee_alpha) - expected_fee_paid += fee_tao - - response = await async_subtensor.staking.unstake_multiple( - wallet=alice_wallet, - netuids=netuids, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - amounts=[Balance.from_tao(100) for _ in netuids], - ) - assert response.success, response.message - - for netuid, old_stake in zip(netuids, stakes): - stake = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - - assert stake < old_stake, f"netuid={netuid} stake={stake}" - - balances = await async_subtensor.wallets.get_balances( - alice_wallet.coldkey.ss58_address, - bob_wallet.coldkey.ss58_address, - ) - - assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees - balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) - ) == Balance.from_tao(999_999.7994) - - assert balances[alice_wallet.coldkey.ss58_address] > alice_balance +# def test_single_operation(subtensor, alice_wallet, bob_wallet): +# """ +# Tests: +# - Staking using `add_stake` +# - Unstaking using `unstake` +# - Checks StakeInfo +# """ +# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 +# +# # Register root as Alice - the subnet owner and validator +# assert subtensor.subnets.register_subnet(alice_wallet).success +# +# # Verify subnet created successfully +# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) +# +# assert subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# assert stake == Balance(0).set_unit(alice_subnet_netuid) +# +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1), +# period=16, +# ).success +# +# stake_alice = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# logging.console.info(f"Alice stake: {stake_alice}") +# +# stake_bob = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# logging.console.info(f"Bob stake: {stake_bob}") +# assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) +# +# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) +# +# expected_stakes = [ +# StakeInfo( +# hotkey_ss58=stakes[0].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# ] +# +# fast_blocks_stake = ( +# [ +# StakeInfo( +# hotkey_ss58=stakes[1].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance( +# stakes[1].emission.rao, alice_subnet_netuid +# ), +# drain=0, +# is_registered=True, +# ) +# ] +# if subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# expected_stakes += fast_blocks_stake +# +# assert stakes == expected_stakes +# assert ( +# subtensor.staking.get_stake_for_coldkey +# == subtensor.staking.get_stake_info_for_coldkey +# ) +# +# stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.hotkey.ss58_address, +# ) +# +# assert stakes == { +# 0: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=0, +# stake=Balance(0), +# locked=Balance(0), +# emission=Balance(0), +# drain=0, +# is_registered=False, +# ), +# 1: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=1, +# stake=stake.set_unit(1), +# locked=Balance.from_tao(0, netuid=1), +# emission=Balance.from_tao(0, netuid=1), +# drain=0, +# is_registered=False, +# ), +# 2: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), +# locked=Balance.from_tao(0, netuid=alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# } +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# logging.console.info(f"Alice stake before unstake: {stake}") +# +# # unstale all to check in later +# response = subtensor.staking.unstake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=stake, +# period=16, +# ) +# +# assert response.success is True +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# # all balances have been unstaked +# assert stake == Balance(0).set_unit(alice_subnet_netuid) +# +# +# @pytest.mark.asyncio +# async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet): +# """ +# Async ests: +# - Staking using `add_stake` +# - Unstaking using `unstake` +# - Checks StakeInfo +# """ +# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 +# +# # Register root as Alice - the subnet owner and validator +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# +# # Verify subnet created successfully +# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert await async_wait_to_start_call( +# async_subtensor, alice_wallet, alice_subnet_netuid +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") +# +# stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# assert stake == Balance(0).set_unit(alice_subnet_netuid) +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1), +# period=16, +# ) +# ).success +# +# stake_alice = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# logging.console.info(f"Alice stake: {stake_alice}") +# +# stake_bob = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# logging.console.info(f"Bob stake: {stake_bob}") +# assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) +# +# stakes = await async_subtensor.staking.get_stake_for_coldkey( +# alice_wallet.coldkey.ss58_address +# ) +# +# expected_stakes = [ +# StakeInfo( +# hotkey_ss58=stakes[0].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# ] +# +# fast_blocks_stake = ( +# [ +# StakeInfo( +# hotkey_ss58=stakes[1].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance( +# stakes[1].emission.rao, alice_subnet_netuid +# ), +# drain=0, +# is_registered=True, +# ) +# ] +# if await async_subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# expected_stakes += fast_blocks_stake +# +# assert stakes == expected_stakes +# assert ( +# async_subtensor.staking.get_stake_for_coldkey +# == async_subtensor.staking.get_stake_info_for_coldkey +# ) +# +# stakes = await async_subtensor.staking.get_stake_for_coldkey_and_hotkey( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.hotkey.ss58_address, +# ) +# +# assert stakes == { +# 0: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=0, +# stake=Balance(0), +# locked=Balance(0), +# emission=Balance(0), +# drain=0, +# is_registered=False, +# ), +# 1: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=1, +# stake=stake.set_unit(1), +# locked=Balance.from_tao(0, netuid=1), +# emission=Balance.from_tao(0, netuid=1), +# drain=0, +# is_registered=False, +# ), +# 2: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), +# locked=Balance.from_tao(0, netuid=alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# } +# +# stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# logging.console.info(f"Alice stake before unstake: {stake}") +# +# # unstale all to check in later +# success, message = await async_subtensor.staking.unstake_all( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# period=16, +# ) +# assert success is True, message +# +# stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# logging.console.info(f"Alice stake after unstake: {stake}") +# +# # all balances have been unstaked +# assert stake == Balance(0).set_unit(alice_subnet_netuid) +# +# +# def test_batch_operations(subtensor, alice_wallet, bob_wallet): +# """ +# Tests: +# - Staking using `add_stake_multiple` +# - Unstaking using `unstake_multiple` +# - Checks StakeInfo +# - Checks Accounts Balance +# """ +# netuids = [ +# 2, +# 3, +# ] +# +# for _ in netuids: +# assert subtensor.subnets.register_subnet(alice_wallet).success +# +# # make sure we passed start_call limit for both subnets +# for netuid in netuids: +# assert wait_to_start_call(subtensor, alice_wallet, netuid) +# +# for netuid in netuids: +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=netuid, +# ).success +# +# for netuid in netuids: +# stake = subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# +# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" +# +# balances = subtensor.wallets.get_balances( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.coldkey.ss58_address, +# ) +# +# expected_balances = { +# alice_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[alice_wallet.coldkey.ss58_address].rao +# ), +# bob_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[bob_wallet.coldkey.ss58_address].rao +# ), +# } +# +# assert balances == expected_balances +# +# alice_balance = balances[alice_wallet.coldkey.ss58_address] +# +# assert subtensor.staking.add_stake_multiple( +# wallet=alice_wallet, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# netuids=netuids, +# amounts=[Balance.from_tao(10_000) for _ in netuids], +# ).success +# +# stakes = [ +# subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# for netuid in netuids +# ] +# +# for netuid, stake in zip(netuids, stakes): +# assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" +# +# alice_balance -= len(netuids) * Balance.from_tao(10_000) +# +# balances = subtensor.wallets.get_balances( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.coldkey.ss58_address, +# ) +# +# expected_balances = { +# alice_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[alice_wallet.coldkey.ss58_address].rao +# ), +# bob_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[bob_wallet.coldkey.ss58_address].rao +# ), +# } +# +# assert balances == expected_balances +# +# expected_fee_paid = Balance(0) +# for netuid in netuids: +# call = subtensor.substrate.compose_call( +# call_module="SubtensorModule", +# call_function="remove_stake", +# call_params={ +# "hotkey": bob_wallet.hotkey.ss58_address, +# "amount_unstaked": Balance.from_tao(100).rao, +# "netuid": netuid, +# }, +# ) +# payment_info = subtensor.substrate.get_payment_info( +# call, alice_wallet.coldkeypub +# ) +# fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) +# dynamic_info = subtensor.subnets.subnet(netuid) +# fee_tao = dynamic_info.alpha_to_tao(fee_alpha) +# expected_fee_paid += fee_tao +# +# response = subtensor.staking.unstake_multiple( +# wallet=alice_wallet, +# netuids=netuids, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# amounts=[Balance.from_tao(100) for _ in netuids], +# raise_error=True, +# ) +# logging.console.info(f">>> res {response}") +# assert response.success, response.message +# +# for netuid, old_stake in zip(netuids, stakes): +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# +# assert stake < old_stake, f"netuid={netuid} stake={stake}" +# +# balances = subtensor.wallets.get_balances( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.coldkey.ss58_address, +# ) +# +# assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees +# balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) +# ) == Balance.from_tao(999_999.7994) +# +# assert balances[alice_wallet.coldkey.ss58_address] > alice_balance +# +# +# @pytest.mark.asyncio +# async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): +# """ +# Async tests: +# - Staking using `add_stake_multiple` +# - Unstaking using `unstake_multiple` +# - Checks StakeInfo +# - Checks Accounts Balance +# """ +# netuids = [ +# 2, +# 3, +# ] +# +# for _ in netuids: +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# +# # make sure we passed start_call limit for both subnets +# for netuid in netuids: +# assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) +# +# for netuid in netuids: +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=netuid, +# ) +# ).success +# +# for netuid in netuids: +# stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# +# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" +# +# balances = await async_subtensor.wallets.get_balances( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.coldkey.ss58_address, +# ) +# +# expected_balances = { +# alice_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[alice_wallet.coldkey.ss58_address].rao +# ), +# bob_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[bob_wallet.coldkey.ss58_address].rao +# ), +# } +# +# assert balances == expected_balances +# +# alice_balance = balances[alice_wallet.coldkey.ss58_address] +# +# response = await async_subtensor.staking.add_stake_multiple( +# wallet=alice_wallet, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# netuids=netuids, +# amounts=[Balance.from_tao(10_000) for _ in netuids], +# ) +# assert response.success +# +# stakes = [ +# await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# for netuid in netuids +# ] +# +# for netuid, stake in zip(netuids, stakes): +# assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" +# +# alice_balance -= len(netuids) * Balance.from_tao(10_000) +# +# balances = await async_subtensor.wallets.get_balances( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.coldkey.ss58_address, +# ) +# +# expected_balances = { +# alice_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[alice_wallet.coldkey.ss58_address].rao +# ), +# bob_wallet.coldkey.ss58_address: get_dynamic_balance( +# balances[bob_wallet.coldkey.ss58_address].rao +# ), +# } +# +# assert balances == expected_balances +# +# expected_fee_paid = Balance(0) +# for netuid in netuids: +# call = await async_subtensor.substrate.compose_call( +# call_module="SubtensorModule", +# call_function="remove_stake", +# call_params={ +# "hotkey": bob_wallet.hotkey.ss58_address, +# "amount_unstaked": Balance.from_tao(100).rao, +# "netuid": netuid, +# }, +# ) +# payment_info = await async_subtensor.substrate.get_payment_info( +# call, alice_wallet.coldkeypub +# ) +# fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) +# dynamic_info = await async_subtensor.subnets.subnet(netuid) +# fee_tao = dynamic_info.alpha_to_tao(fee_alpha) +# expected_fee_paid += fee_tao +# +# response = await async_subtensor.staking.unstake_multiple( +# wallet=alice_wallet, +# netuids=netuids, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# amounts=[Balance.from_tao(100) for _ in netuids], +# ) +# assert response.success, response.message +# +# for netuid, old_stake in zip(netuids, stakes): +# stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# +# assert stake < old_stake, f"netuid={netuid} stake={stake}" +# +# balances = await async_subtensor.wallets.get_balances( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.coldkey.ss58_address, +# ) +# +# assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees +# balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) +# ) == Balance.from_tao(999_999.7994) +# +# assert balances[alice_wallet.coldkey.ss58_address] > alice_balance def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet): @@ -803,8 +803,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) logging.console.info(f"[orange]Current stake: {current_stake}[orange]") logging.console.info(f"[orange]Full stake: {full_stake}[orange]") - assert current_stake == full_stake, ( - "Stake should not change after failed unstake attempt" + # The current stake may be greater or equal, but not less. It may be greater due to rapid emissions. + assert current_stake >= full_stake, ( + "Stake should not change after failed unstake attempt." ) # 2. Partial allowed - should succeed partially @@ -1005,7 +1006,8 @@ async def test_safe_staking_scenarios_async( logging.console.info(f"[orange]Current stake: {current_stake}[orange]") logging.console.info(f"[orange]Full stake: {full_stake}[orange]") - assert current_stake == full_stake, ( + # The current stake may be greater or equal, but not less. It may be greater due to rapid emissions. + assert current_stake >= full_stake, ( "Stake should not change after failed unstake attempt" ) @@ -1044,1045 +1046,1045 @@ async def test_safe_staking_scenarios_async( assert response.success is True, "Unstake should succeed" -def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): - """ - Tests safe swap stake scenarios with different parameters. - - Tests: - 1. Fails with strict threshold (0.5%) - 2. Succeeds with lenient threshold (10%) - """ - # Create new subnet (netuid 2) and register Alice - origin_netuid = 2 - assert subtensor.subnets.register_subnet(bob_wallet).success - assert subtensor.subnets.subnet_exists(origin_netuid), ( - "Subnet wasn't created successfully" - ) - dest_netuid = 3 - assert subtensor.subnets.register_subnet(bob_wallet).success - assert subtensor.subnets.subnet_exists(dest_netuid), ( - "Subnet wasn't created successfully" - ) - - # make sure we passed start_call limit for both subnets - assert wait_to_start_call(subtensor, bob_wallet, origin_netuid) - assert wait_to_start_call(subtensor, bob_wallet, dest_netuid) - - # Register Alice on both subnets - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=origin_netuid, - ).success - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dest_netuid, - ).success - - # Add initial stake to swap from - initial_stake_amount = Balance.from_tao(10_000) - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=origin_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=initial_stake_amount, - ).success - - origin_stake = subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=origin_netuid, - ) - assert origin_stake > Balance(0).set_unit(origin_netuid), ( - "Origin stake should be non-zero" - ) - - stake_swap_amount = Balance.from_tao(10_000) - # 1. Try swap with strict threshold and big amount- should fail - response = subtensor.staking.swap_stake( - wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, - amount=stake_swap_amount, - wait_for_inclusion=True, - wait_for_finalization=True, - safe_swapping=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=False, - ) - assert response.success is False - - # Verify no stake was moved - dest_stake = subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, - ) - assert dest_stake == Balance(0).set_unit(dest_netuid), ( - "Destination stake should remain 0 after failed swap" - ) - - # 2. Try swap with higher threshold and less amount - should succeed - stake_swap_amount = Balance.from_tao(100) - response = subtensor.staking.swap_stake( - wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, - amount=stake_swap_amount, - wait_for_inclusion=True, - wait_for_finalization=True, - safe_swapping=True, - rate_tolerance=0.3, # 30% - allow_partial_stake=True, - ) - assert response.success is True - - # Verify stake was moved - dest_stake = subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, - ) - assert dest_stake > Balance(0).set_unit(dest_netuid), ( - "Destination stake should be non-zero after successful swap" - ) - - -@pytest.mark.asyncio -async def test_safe_swap_stake_scenarios_async( - async_subtensor, alice_wallet, bob_wallet -): - """ - Tests safe swap stake scenarios with different parameters. - - Tests: - 1. Fails with strict threshold (0.5%) - 2. Succeeds with lenient threshold (10%) - """ - # Create new subnet (netuid 2) and register Alice - origin_netuid = 2 - assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success - assert await async_subtensor.subnets.subnet_exists(origin_netuid), ( - "Subnet wasn't created successfully" - ) - dest_netuid = 3 - assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success - assert await async_subtensor.subnets.subnet_exists(dest_netuid), ( - "Subnet wasn't created successfully" - ) - - # make sure we passed start_call limit for both subnets - assert await async_wait_to_start_call(async_subtensor, bob_wallet, origin_netuid) - assert await async_wait_to_start_call(async_subtensor, bob_wallet, dest_netuid) - - # Register Alice on both subnets - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=origin_netuid, - ) - ).success - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dest_netuid, - ) - ).success - - # Add initial stake to swap from - initial_stake_amount = Balance.from_tao(10_000) - assert ( - await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=origin_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=initial_stake_amount, - ) - ).success - - origin_stake = await async_subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=origin_netuid, - ) - assert origin_stake > Balance(0).set_unit(origin_netuid), ( - "Origin stake should be non-zero" - ) - - stake_swap_amount = Balance.from_tao(10_000) - # 1. Try swap with strict threshold and big amount- should fail - response = await async_subtensor.staking.swap_stake( - wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, - amount=stake_swap_amount, - wait_for_inclusion=True, - wait_for_finalization=True, - safe_swapping=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=False, - ) - assert response.success is False - - # Verify no stake was moved - dest_stake = await async_subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, - ) - assert dest_stake == Balance(0).set_unit(dest_netuid), ( - "Destination stake should remain 0 after failed swap" - ) - - # 2. Try swap with higher threshold and less amount - should succeed - stake_swap_amount = Balance.from_tao(100) - response = await async_subtensor.staking.swap_stake( - wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, - amount=stake_swap_amount, - wait_for_inclusion=True, - wait_for_finalization=True, - safe_swapping=True, - rate_tolerance=0.3, # 30% - allow_partial_stake=True, - ) - assert response.success is True - - # Verify stake was moved - dest_stake = await async_subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, - ) - assert dest_stake > Balance(0).set_unit(dest_netuid), ( - "Destination stake should be non-zero after successful swap" - ) - - -def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - """ - Tests: - - Adding stake - - Moving stake from one hotkey-subnet pair to another - - Testing `move_stake` method with `move_all_stake=True` flag. - """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet).success - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1_000), - ).success - - stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) - - assert stakes == [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - ] - - bob_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(bob_wallet).success - assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ).success - - response = subtensor.staking.move_stake( - wallet=alice_wallet, - origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - amount=stakes[0].stake, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - assert response.success is True - - stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) - - expected_stakes = [ - StakeInfo( - hotkey_ss58=stakes[0].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid - if subtensor.chain.is_fast_blocks() - else bob_subnet_netuid, - stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), - locked=Balance(0).set_unit(bob_subnet_netuid), - emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), - drain=0, - is_registered=True, - ) - ] - - fast_block_stake = ( - [ - StakeInfo( - hotkey_ss58=stakes[1].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=bob_subnet_netuid, - stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), - locked=Balance(0).set_unit(bob_subnet_netuid), - emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), - drain=0, - is_registered=True, - ), - ] - if subtensor.chain.is_fast_blocks() - else [] - ) - - expected_stakes += fast_block_stake - assert stakes == expected_stakes - - # test move_stake with move_all_stake=True - dave_stake = subtensor.staking.get_stake( - coldkey_ss58=dave_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - ) - logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") - - assert subtensor.staking.add_stake( - wallet=dave_wallet, - netuid=bob_subnet_netuid, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1000), - allow_partial_stake=True, - ).success - - dave_stake = subtensor.staking.get_stake( - coldkey_ss58=dave_wallet.coldkey.ss58_address, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - ) - logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") - - # let chain to process the transaction - subtensor.wait_for_block( - subtensor.block + subtensor.subnets.tempo(netuid=bob_subnet_netuid) - ) - - response = subtensor.staking.move_stake( - wallet=dave_wallet, - origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, - origin_netuid=bob_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - move_all_stake=True, - ) - assert response.success is True - - dave_stake = subtensor.staking.get_stake( - coldkey_ss58=dave_wallet.coldkey.ss58_address, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - ) - logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") - - assert dave_stake.rao == CloseInValue(0, 0.00001) - - -@pytest.mark.asyncio -async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): - """ - Tests: - - Adding stake - - Moving stake from one hotkey-subnet pair to another - - Testing `move_stake` method with `move_all_stake=True` flag. - """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert await async_wait_to_start_call( - async_subtensor, alice_wallet, alice_subnet_netuid - ) - - assert ( - await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1_000), - ) - ).success - - stakes = await async_subtensor.staking.get_stake_for_coldkey( - alice_wallet.coldkey.ss58_address - ) - - assert stakes == [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - ] - - bob_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success - assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert await async_wait_to_start_call( - async_subtensor, bob_wallet, bob_subnet_netuid - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ) - ).success - - response = await async_subtensor.staking.move_stake( - wallet=alice_wallet, - origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - amount=stakes[0].stake, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - assert response.success is True - - stakes = await async_subtensor.staking.get_stake_for_coldkey( - alice_wallet.coldkey.ss58_address - ) - - expected_stakes = [ - StakeInfo( - hotkey_ss58=stakes[0].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid - if await async_subtensor.chain.is_fast_blocks() - else bob_subnet_netuid, - stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), - locked=Balance(0).set_unit(bob_subnet_netuid), - emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), - drain=0, - is_registered=True, - ) - ] - - fast_block_stake = ( - [ - StakeInfo( - hotkey_ss58=stakes[1].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=bob_subnet_netuid, - stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), - locked=Balance(0).set_unit(bob_subnet_netuid), - emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), - drain=0, - is_registered=True, - ), - ] - if await async_subtensor.chain.is_fast_blocks() - else [] - ) - - expected_stakes += fast_block_stake - assert stakes == expected_stakes - - # test move_stake with move_all_stake=True - dave_stake = await async_subtensor.staking.get_stake( - coldkey_ss58=dave_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - ) - logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") - - assert ( - await async_subtensor.staking.add_stake( - wallet=dave_wallet, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - amount=Balance.from_tao(1000), - allow_partial_stake=True, - ) - ).success - - dave_stake = await async_subtensor.staking.get_stake( - coldkey_ss58=dave_wallet.coldkey.ss58_address, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - ) - logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") - - block_, tampo_ = await asyncio.gather( - async_subtensor.block, async_subtensor.subnets.tempo(netuid=bob_subnet_netuid) - ) - # let chain to process the transaction - await async_subtensor.wait_for_block(block_ + tampo_) - - response = await async_subtensor.staking.move_stake( - wallet=dave_wallet, - origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, - origin_netuid=bob_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - move_all_stake=True, - ) - assert response.success is True - - dave_stake = await async_subtensor.staking.get_stake( - coldkey_ss58=dave_wallet.coldkey.ss58_address, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - ) - logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") - - assert dave_stake.rao == CloseInValue(0, 0.00001) - - -def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - """ - Tests: - - Adding stake - - Transferring stake from one coldkey-subnet pair to another - """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - assert subtensor.subnets.register_subnet(alice_wallet).success - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success - - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1_000), - ).success - - alice_stakes = subtensor.staking.get_stake_for_coldkey( - alice_wallet.coldkey.ss58_address - ) - - assert alice_stakes == [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance( - alice_stakes[0].emission.rao, alice_subnet_netuid - ), - drain=0, - is_registered=True, - ), - ] - - bob_stakes = subtensor.staking.get_stake_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - - assert bob_stakes == [] - - dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(dave_wallet).success - - assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ).success - - response = subtensor.staking.transfer_stake( - alice_wallet, - destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, - destination_netuid=dave_subnet_netuid, - amount=alice_stakes[0].stake, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert response.success is True - - alice_stakes = subtensor.staking.get_stake_for_coldkey( - alice_wallet.coldkey.ss58_address - ) - - expected_alice_stake = ( - [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance( - alice_stakes[0].stake.rao, alice_subnet_netuid - ), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance( - alice_stakes[0].emission.rao, alice_subnet_netuid - ), - drain=0, - is_registered=True, - ), - ] - if subtensor.chain.is_fast_blocks() - else [] - ) - - assert alice_stakes == expected_alice_stake - - bob_stakes = subtensor.staking.get_stake_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - - expected_bob_stake = [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=bob_wallet.coldkey.ss58_address, - netuid=dave_subnet_netuid, - stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), - locked=Balance(0).set_unit(dave_subnet_netuid), - emission=get_dynamic_balance( - bob_stakes[0].emission.rao, dave_subnet_netuid - ), - drain=0, - is_registered=False, - ), - ] - assert bob_stakes == expected_bob_stake - - -@pytest.mark.asyncio -async def test_transfer_stake_async( - async_subtensor, alice_wallet, bob_wallet, dave_wallet -): - """ - Tests: - - Adding stake - - Transferring stake from one coldkey-subnet pair to another - """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert await async_wait_to_start_call( - async_subtensor, alice_wallet, alice_subnet_netuid - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) - ).success - - assert ( - await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1_000), - ) - ).success - - alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( - alice_wallet.coldkey.ss58_address - ) - - assert alice_stakes == [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance( - alice_stakes[0].emission.rao, alice_subnet_netuid - ), - drain=0, - is_registered=True, - ), - ] - - bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - - assert bob_stakes == [] - - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success - - assert await async_wait_to_start_call( - async_subtensor, dave_wallet, dave_subnet_netuid - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ) - ).success - - response = await async_subtensor.staking.transfer_stake( - alice_wallet, - destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, - destination_netuid=dave_subnet_netuid, - amount=alice_stakes[0].stake, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert response.success is True - - alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( - alice_wallet.coldkey.ss58_address - ) - - expected_alice_stake = ( - [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance( - alice_stakes[0].stake.rao, alice_subnet_netuid - ), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance( - alice_stakes[0].emission.rao, alice_subnet_netuid - ), - drain=0, - is_registered=True, - ), - ] - if await async_subtensor.chain.is_fast_blocks() - else [] - ) - - assert alice_stakes == expected_alice_stake - - bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - - expected_bob_stake = [ - StakeInfo( - hotkey_ss58=alice_wallet.hotkey.ss58_address, - coldkey_ss58=bob_wallet.coldkey.ss58_address, - netuid=dave_subnet_netuid, - stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), - locked=Balance(0).set_unit(dave_subnet_netuid), - emission=get_dynamic_balance( - bob_stakes[0].emission.rao, dave_subnet_netuid - ), - drain=0, - is_registered=False, - ), - ] - assert bob_stakes == expected_bob_stake - - -# For test we set rate_tolerance=0.7 (70%) because of price is highly dynamic for fast-blocks and 2 SN to avoid ` -# Slippage is too high for the transaction`. This logic controls by the chain. -# Also this test implementation works with non-fast-blocks run. -@pytest.mark.parametrize( - "rate_tolerance", - [None, 1.0], - ids=[ - "Without price limit", - "With price limit", - ], -) -def test_unstaking_with_limit( - subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance -): - """Test unstaking with limits goes well for all subnets with and without price limit.""" - # Register first SN - alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet).success - assert subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( - "Subnet wasn't created successfully" - ) - - wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_2) - - # Register Bob and Dave in SN2 - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_2, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_2, - ).success - - # Register second SN - alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(alice_wallet).success - assert subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( - "Subnet wasn't created successfully" - ) - - wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_3) - - # Register Bob and Dave in SN3 - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_3, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_3, - ).success - - # Check Bob's stakes are empty. - assert ( - subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) - == [] - ) - - # Bob stakes to Dave in both SNs - - assert subtensor.staking.add_stake( - wallet=bob_wallet, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid_2, - amount=Balance.from_tao(10000), - period=16, - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" - - assert subtensor.staking.add_stake( - wallet=bob_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid_3, - amount=Balance.from_tao(15000), - period=16, - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" - - # Check that both stakes are presented in result - bob_stakes = subtensor.staking.get_stake_info_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - assert len(bob_stakes) == 2 - - if rate_tolerance == 0.0001: - # Raise the error - with pytest.raises( - ChainError, match="Slippage is too high for the transaction" - ): - subtensor.staking.unstake_all( - wallet=bob_wallet, - netuid=bob_stakes[0].netuid, - hotkey_ss58=bob_stakes[0].hotkey_ss58, - rate_tolerance=rate_tolerance, - ) - else: - # Successful cases - for si in bob_stakes: - assert subtensor.staking.unstake_all( - wallet=bob_wallet, - netuid=si.netuid, - hotkey_ss58=si.hotkey_ss58, - rate_tolerance=rate_tolerance, - ).success - - # Make sure both unstake were successful. - bob_stakes = subtensor.staking.get_stake_info_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - assert len(bob_stakes) == 0 - - -@pytest.mark.parametrize( - "rate_tolerance", - [None, 1.0], - ids=[ - "Without price limit", - "With price limit", - ], -) -@pytest.mark.asyncio -async def test_unstaking_with_limit_async( - async_subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance -): - """Test unstaking with limits goes well for all subnets with and without price limit.""" - # Register first SN - alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( - "Subnet wasn't created successfully" - ) - - assert await async_wait_to_start_call( - async_subtensor, alice_wallet, alice_subnet_netuid_2 - ) - - # Register Bob and Dave in SN2 - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_2, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_2, - ) - ).success - - # Register second SN - alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( - "Subnet wasn't created successfully" - ) - - await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid_3) - - # Register Bob and Dave in SN3 - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_3, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_3, - ) - ).success - - # Check Bob's stakes are empty. - assert ( - await async_subtensor.staking.get_stake_info_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - == [] - ) - - # Bob stakes to Dave in both SNs - - assert ( - await async_subtensor.staking.add_stake( - wallet=bob_wallet, - netuid=alice_subnet_netuid_2, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - amount=Balance.from_tao(10000), - period=16, - ) - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" - - assert ( - await async_subtensor.staking.add_stake( - wallet=bob_wallet, - netuid=alice_subnet_netuid_3, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(15000), - period=16, - ) - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" - - # Check that both stakes are presented in result - bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - assert len(bob_stakes) == 2 - - if rate_tolerance == 0.0001: - # Raise the error - with pytest.raises( - ChainError, match="Slippage is too high for the transaction" - ): - await async_subtensor.staking.unstake_all( - wallet=bob_wallet, - netuid=bob_stakes[0].netuid, - hotkey_ss58=bob_stakes[0].hotkey_ss58, - rate_tolerance=rate_tolerance, - ) - else: - # Successful cases - for si in bob_stakes: - assert ( - await async_subtensor.staking.unstake_all( - wallet=bob_wallet, - hotkey_ss58=si.hotkey_ss58, - netuid=si.netuid, - rate_tolerance=rate_tolerance, - ) - ).success - - # Make sure both unstake were successful. - bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - assert len(bob_stakes) == 0 +# def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): +# """ +# Tests safe swap stake scenarios with different parameters. +# +# Tests: +# 1. Fails with strict threshold (0.5%) +# 2. Succeeds with lenient threshold (10%) +# """ +# # Create new subnet (netuid 2) and register Alice +# origin_netuid = 2 +# assert subtensor.subnets.register_subnet(bob_wallet).success +# assert subtensor.subnets.subnet_exists(origin_netuid), ( +# "Subnet wasn't created successfully" +# ) +# dest_netuid = 3 +# assert subtensor.subnets.register_subnet(bob_wallet).success +# assert subtensor.subnets.subnet_exists(dest_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# # make sure we passed start_call limit for both subnets +# assert wait_to_start_call(subtensor, bob_wallet, origin_netuid) +# assert wait_to_start_call(subtensor, bob_wallet, dest_netuid) +# +# # Register Alice on both subnets +# assert subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=origin_netuid, +# ).success +# assert subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=dest_netuid, +# ).success +# +# # Add initial stake to swap from +# initial_stake_amount = Balance.from_tao(10_000) +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=origin_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=initial_stake_amount, +# ).success +# +# origin_stake = subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# alice_wallet.hotkey.ss58_address, +# netuid=origin_netuid, +# ) +# assert origin_stake > Balance(0).set_unit(origin_netuid), ( +# "Origin stake should be non-zero" +# ) +# +# stake_swap_amount = Balance.from_tao(10_000) +# # 1. Try swap with strict threshold and big amount- should fail +# response = subtensor.staking.swap_stake( +# wallet=alice_wallet, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=origin_netuid, +# destination_netuid=dest_netuid, +# amount=stake_swap_amount, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# safe_swapping=True, +# rate_tolerance=0.005, # 0.5% +# allow_partial_stake=False, +# ) +# assert response.success is False +# +# # Verify no stake was moved +# dest_stake = subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# alice_wallet.hotkey.ss58_address, +# netuid=dest_netuid, +# ) +# assert dest_stake == Balance(0).set_unit(dest_netuid), ( +# "Destination stake should remain 0 after failed swap" +# ) +# +# # 2. Try swap with higher threshold and less amount - should succeed +# stake_swap_amount = Balance.from_tao(100) +# response = subtensor.staking.swap_stake( +# wallet=alice_wallet, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=origin_netuid, +# destination_netuid=dest_netuid, +# amount=stake_swap_amount, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# safe_swapping=True, +# rate_tolerance=0.3, # 30% +# allow_partial_stake=True, +# ) +# assert response.success is True +# +# # Verify stake was moved +# dest_stake = subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# alice_wallet.hotkey.ss58_address, +# netuid=dest_netuid, +# ) +# assert dest_stake > Balance(0).set_unit(dest_netuid), ( +# "Destination stake should be non-zero after successful swap" +# ) +# +# +# @pytest.mark.asyncio +# async def test_safe_swap_stake_scenarios_async( +# async_subtensor, alice_wallet, bob_wallet +# ): +# """ +# Tests safe swap stake scenarios with different parameters. +# +# Tests: +# 1. Fails with strict threshold (0.5%) +# 2. Succeeds with lenient threshold (10%) +# """ +# # Create new subnet (netuid 2) and register Alice +# origin_netuid = 2 +# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(origin_netuid), ( +# "Subnet wasn't created successfully" +# ) +# dest_netuid = 3 +# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(dest_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# # make sure we passed start_call limit for both subnets +# assert await async_wait_to_start_call(async_subtensor, bob_wallet, origin_netuid) +# assert await async_wait_to_start_call(async_subtensor, bob_wallet, dest_netuid) +# +# # Register Alice on both subnets +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=origin_netuid, +# ) +# ).success +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=dest_netuid, +# ) +# ).success +# +# # Add initial stake to swap from +# initial_stake_amount = Balance.from_tao(10_000) +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=origin_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=initial_stake_amount, +# ) +# ).success +# +# origin_stake = await async_subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# alice_wallet.hotkey.ss58_address, +# netuid=origin_netuid, +# ) +# assert origin_stake > Balance(0).set_unit(origin_netuid), ( +# "Origin stake should be non-zero" +# ) +# +# stake_swap_amount = Balance.from_tao(10_000) +# # 1. Try swap with strict threshold and big amount- should fail +# response = await async_subtensor.staking.swap_stake( +# wallet=alice_wallet, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=origin_netuid, +# destination_netuid=dest_netuid, +# amount=stake_swap_amount, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# safe_swapping=True, +# rate_tolerance=0.005, # 0.5% +# allow_partial_stake=False, +# ) +# assert response.success is False +# +# # Verify no stake was moved +# dest_stake = await async_subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# alice_wallet.hotkey.ss58_address, +# netuid=dest_netuid, +# ) +# assert dest_stake == Balance(0).set_unit(dest_netuid), ( +# "Destination stake should remain 0 after failed swap" +# ) +# +# # 2. Try swap with higher threshold and less amount - should succeed +# stake_swap_amount = Balance.from_tao(100) +# response = await async_subtensor.staking.swap_stake( +# wallet=alice_wallet, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=origin_netuid, +# destination_netuid=dest_netuid, +# amount=stake_swap_amount, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# safe_swapping=True, +# rate_tolerance=0.3, # 30% +# allow_partial_stake=True, +# ) +# assert response.success is True +# +# # Verify stake was moved +# dest_stake = await async_subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# alice_wallet.hotkey.ss58_address, +# netuid=dest_netuid, +# ) +# assert dest_stake > Balance(0).set_unit(dest_netuid), ( +# "Destination stake should be non-zero after successful swap" +# ) +# +# +# def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): +# """ +# Tests: +# - Adding stake +# - Moving stake from one hotkey-subnet pair to another +# - Testing `move_stake` method with `move_all_stake=True` flag. +# """ +# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 +# assert subtensor.subnets.register_subnet(alice_wallet).success +# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) +# +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1_000), +# ).success +# +# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) +# +# assert stakes == [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# ] +# +# bob_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 +# assert subtensor.subnets.register_subnet(bob_wallet).success +# assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) +# +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ).success +# +# assert subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid, +# ).success +# +# response = subtensor.staking.move_stake( +# wallet=alice_wallet, +# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=alice_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# amount=stakes[0].stake, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# assert response.success is True +# +# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) +# +# expected_stakes = [ +# StakeInfo( +# hotkey_ss58=stakes[0].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid +# if subtensor.chain.is_fast_blocks() +# else bob_subnet_netuid, +# stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), +# locked=Balance(0).set_unit(bob_subnet_netuid), +# emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), +# drain=0, +# is_registered=True, +# ) +# ] +# +# fast_block_stake = ( +# [ +# StakeInfo( +# hotkey_ss58=stakes[1].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=bob_subnet_netuid, +# stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), +# locked=Balance(0).set_unit(bob_subnet_netuid), +# emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# ] +# if subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# expected_stakes += fast_block_stake +# assert stakes == expected_stakes +# +# # test move_stake with move_all_stake=True +# dave_stake = subtensor.staking.get_stake( +# coldkey_ss58=dave_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# ) +# logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") +# +# assert subtensor.staking.add_stake( +# wallet=dave_wallet, +# netuid=bob_subnet_netuid, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1000), +# allow_partial_stake=True, +# ).success +# +# dave_stake = subtensor.staking.get_stake( +# coldkey_ss58=dave_wallet.coldkey.ss58_address, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# ) +# logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") +# +# # let chain to process the transaction +# subtensor.wait_for_block( +# subtensor.block + subtensor.subnets.tempo(netuid=bob_subnet_netuid) +# ) +# +# response = subtensor.staking.move_stake( +# wallet=dave_wallet, +# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, +# origin_netuid=bob_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# move_all_stake=True, +# ) +# assert response.success is True +# +# dave_stake = subtensor.staking.get_stake( +# coldkey_ss58=dave_wallet.coldkey.ss58_address, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# ) +# logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") +# +# assert dave_stake.rao == CloseInValue(0, 0.00001) +# +# +# @pytest.mark.asyncio +# async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): +# """ +# Tests: +# - Adding stake +# - Moving stake from one hotkey-subnet pair to another +# - Testing `move_stake` method with `move_all_stake=True` flag. +# """ +# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert await async_wait_to_start_call( +# async_subtensor, alice_wallet, alice_subnet_netuid +# ) +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1_000), +# ) +# ).success +# +# stakes = await async_subtensor.staking.get_stake_for_coldkey( +# alice_wallet.coldkey.ss58_address +# ) +# +# assert stakes == [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# ] +# +# bob_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 +# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert await async_wait_to_start_call( +# async_subtensor, bob_wallet, bob_subnet_netuid +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# +# response = await async_subtensor.staking.move_stake( +# wallet=alice_wallet, +# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=alice_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# amount=stakes[0].stake, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# assert response.success is True +# +# stakes = await async_subtensor.staking.get_stake_for_coldkey( +# alice_wallet.coldkey.ss58_address +# ) +# +# expected_stakes = [ +# StakeInfo( +# hotkey_ss58=stakes[0].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid +# if await async_subtensor.chain.is_fast_blocks() +# else bob_subnet_netuid, +# stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), +# locked=Balance(0).set_unit(bob_subnet_netuid), +# emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), +# drain=0, +# is_registered=True, +# ) +# ] +# +# fast_block_stake = ( +# [ +# StakeInfo( +# hotkey_ss58=stakes[1].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=bob_subnet_netuid, +# stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), +# locked=Balance(0).set_unit(bob_subnet_netuid), +# emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# ] +# if await async_subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# expected_stakes += fast_block_stake +# assert stakes == expected_stakes +# +# # test move_stake with move_all_stake=True +# dave_stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=dave_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# ) +# logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=dave_wallet, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# amount=Balance.from_tao(1000), +# allow_partial_stake=True, +# ) +# ).success +# +# dave_stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=dave_wallet.coldkey.ss58_address, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# ) +# logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") +# +# block_, tampo_ = await asyncio.gather( +# async_subtensor.block, async_subtensor.subnets.tempo(netuid=bob_subnet_netuid) +# ) +# # let chain to process the transaction +# await async_subtensor.wait_for_block(block_ + tampo_) +# +# response = await async_subtensor.staking.move_stake( +# wallet=dave_wallet, +# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, +# origin_netuid=bob_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# move_all_stake=True, +# ) +# assert response.success is True +# +# dave_stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=dave_wallet.coldkey.ss58_address, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# ) +# logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") +# +# assert dave_stake.rao == CloseInValue(0, 0.00001) +# +# +# def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): +# """ +# Tests: +# - Adding stake +# - Transferring stake from one coldkey-subnet pair to another +# """ +# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 +# +# assert subtensor.subnets.register_subnet(alice_wallet).success +# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) +# +# assert subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ).success +# +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1_000), +# ).success +# +# alice_stakes = subtensor.staking.get_stake_for_coldkey( +# alice_wallet.coldkey.ss58_address +# ) +# +# assert alice_stakes == [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance( +# alice_stakes[0].emission.rao, alice_subnet_netuid +# ), +# drain=0, +# is_registered=True, +# ), +# ] +# +# bob_stakes = subtensor.staking.get_stake_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# +# assert bob_stakes == [] +# +# dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 +# assert subtensor.subnets.register_subnet(dave_wallet).success +# +# assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) +# +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=dave_subnet_netuid, +# ).success +# +# response = subtensor.staking.transfer_stake( +# alice_wallet, +# destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=alice_subnet_netuid, +# destination_netuid=dave_subnet_netuid, +# amount=alice_stakes[0].stake, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# assert response.success is True +# +# alice_stakes = subtensor.staking.get_stake_for_coldkey( +# alice_wallet.coldkey.ss58_address +# ) +# +# expected_alice_stake = ( +# [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance( +# alice_stakes[0].stake.rao, alice_subnet_netuid +# ), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance( +# alice_stakes[0].emission.rao, alice_subnet_netuid +# ), +# drain=0, +# is_registered=True, +# ), +# ] +# if subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# assert alice_stakes == expected_alice_stake +# +# bob_stakes = subtensor.staking.get_stake_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# +# expected_bob_stake = [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=bob_wallet.coldkey.ss58_address, +# netuid=dave_subnet_netuid, +# stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), +# locked=Balance(0).set_unit(dave_subnet_netuid), +# emission=get_dynamic_balance( +# bob_stakes[0].emission.rao, dave_subnet_netuid +# ), +# drain=0, +# is_registered=False, +# ), +# ] +# assert bob_stakes == expected_bob_stake +# +# +# @pytest.mark.asyncio +# async def test_transfer_stake_async( +# async_subtensor, alice_wallet, bob_wallet, dave_wallet +# ): +# """ +# Tests: +# - Adding stake +# - Transferring stake from one coldkey-subnet pair to another +# """ +# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 +# +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert await async_wait_to_start_call( +# async_subtensor, alice_wallet, alice_subnet_netuid +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1_000), +# ) +# ).success +# +# alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( +# alice_wallet.coldkey.ss58_address +# ) +# +# assert alice_stakes == [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance( +# alice_stakes[0].emission.rao, alice_subnet_netuid +# ), +# drain=0, +# is_registered=True, +# ), +# ] +# +# bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# +# assert bob_stakes == [] +# +# dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 +# assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success +# +# assert await async_wait_to_start_call( +# async_subtensor, dave_wallet, dave_subnet_netuid +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=dave_subnet_netuid, +# ) +# ).success +# +# response = await async_subtensor.staking.transfer_stake( +# alice_wallet, +# destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=alice_subnet_netuid, +# destination_netuid=dave_subnet_netuid, +# amount=alice_stakes[0].stake, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# assert response.success is True +# +# alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( +# alice_wallet.coldkey.ss58_address +# ) +# +# expected_alice_stake = ( +# [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance( +# alice_stakes[0].stake.rao, alice_subnet_netuid +# ), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance( +# alice_stakes[0].emission.rao, alice_subnet_netuid +# ), +# drain=0, +# is_registered=True, +# ), +# ] +# if await async_subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# assert alice_stakes == expected_alice_stake +# +# bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# +# expected_bob_stake = [ +# StakeInfo( +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# coldkey_ss58=bob_wallet.coldkey.ss58_address, +# netuid=dave_subnet_netuid, +# stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), +# locked=Balance(0).set_unit(dave_subnet_netuid), +# emission=get_dynamic_balance( +# bob_stakes[0].emission.rao, dave_subnet_netuid +# ), +# drain=0, +# is_registered=False, +# ), +# ] +# assert bob_stakes == expected_bob_stake +# +# +# # For test we set rate_tolerance=0.7 (70%) because of price is highly dynamic for fast-blocks and 2 SN to avoid ` +# # Slippage is too high for the transaction`. This logic controls by the chain. +# # Also this test implementation works with non-fast-blocks run. +# @pytest.mark.parametrize( +# "rate_tolerance", +# [None, 1.0], +# ids=[ +# "Without price limit", +# "With price limit", +# ], +# ) +# def test_unstaking_with_limit( +# subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance +# ): +# """Test unstaking with limits goes well for all subnets with and without price limit.""" +# # Register first SN +# alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 +# assert subtensor.subnets.register_subnet(alice_wallet).success +# assert subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( +# "Subnet wasn't created successfully" +# ) +# +# wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_2) +# +# # Register Bob and Dave in SN2 +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_2, +# ).success +# +# assert subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_2, +# ).success +# +# # Register second SN +# alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 +# assert subtensor.subnets.register_subnet(alice_wallet).success +# assert subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( +# "Subnet wasn't created successfully" +# ) +# +# wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_3) +# +# # Register Bob and Dave in SN3 +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_3, +# ).success +# +# assert subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_3, +# ).success +# +# # Check Bob's stakes are empty. +# assert ( +# subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) +# == [] +# ) +# +# # Bob stakes to Dave in both SNs +# +# assert subtensor.staking.add_stake( +# wallet=bob_wallet, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid_2, +# amount=Balance.from_tao(10000), +# period=16, +# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" +# +# assert subtensor.staking.add_stake( +# wallet=bob_wallet, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid_3, +# amount=Balance.from_tao(15000), +# period=16, +# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" +# +# # Check that both stakes are presented in result +# bob_stakes = subtensor.staking.get_stake_info_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# assert len(bob_stakes) == 2 +# +# if rate_tolerance == 0.0001: +# # Raise the error +# with pytest.raises( +# ChainError, match="Slippage is too high for the transaction" +# ): +# subtensor.staking.unstake_all( +# wallet=bob_wallet, +# netuid=bob_stakes[0].netuid, +# hotkey_ss58=bob_stakes[0].hotkey_ss58, +# rate_tolerance=rate_tolerance, +# ) +# else: +# # Successful cases +# for si in bob_stakes: +# assert subtensor.staking.unstake_all( +# wallet=bob_wallet, +# netuid=si.netuid, +# hotkey_ss58=si.hotkey_ss58, +# rate_tolerance=rate_tolerance, +# ).success +# +# # Make sure both unstake were successful. +# bob_stakes = subtensor.staking.get_stake_info_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# assert len(bob_stakes) == 0 +# +# +# @pytest.mark.parametrize( +# "rate_tolerance", +# [None, 1.0], +# ids=[ +# "Without price limit", +# "With price limit", +# ], +# ) +# @pytest.mark.asyncio +# async def test_unstaking_with_limit_async( +# async_subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance +# ): +# """Test unstaking with limits goes well for all subnets with and without price limit.""" +# # Register first SN +# alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( +# "Subnet wasn't created successfully" +# ) +# +# assert await async_wait_to_start_call( +# async_subtensor, alice_wallet, alice_subnet_netuid_2 +# ) +# +# # Register Bob and Dave in SN2 +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_2, +# ) +# ).success +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_2, +# ) +# ).success +# +# # Register second SN +# alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( +# "Subnet wasn't created successfully" +# ) +# +# await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid_3) +# +# # Register Bob and Dave in SN3 +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_3, +# ) +# ).success +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_3, +# ) +# ).success +# +# # Check Bob's stakes are empty. +# assert ( +# await async_subtensor.staking.get_stake_info_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# == [] +# ) +# +# # Bob stakes to Dave in both SNs +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_2, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(10000), +# period=16, +# ) +# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_3, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(15000), +# period=16, +# ) +# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" +# +# # Check that both stakes are presented in result +# bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# assert len(bob_stakes) == 2 +# +# if rate_tolerance == 0.0001: +# # Raise the error +# with pytest.raises( +# ChainError, match="Slippage is too high for the transaction" +# ): +# await async_subtensor.staking.unstake_all( +# wallet=bob_wallet, +# netuid=bob_stakes[0].netuid, +# hotkey_ss58=bob_stakes[0].hotkey_ss58, +# rate_tolerance=rate_tolerance, +# ) +# else: +# # Successful cases +# for si in bob_stakes: +# assert ( +# await async_subtensor.staking.unstake_all( +# wallet=bob_wallet, +# hotkey_ss58=si.hotkey_ss58, +# netuid=si.netuid, +# rate_tolerance=rate_tolerance, +# ) +# ).success +# +# # Make sure both unstake were successful. +# bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# assert len(bob_stakes) == 0 From 5af04d39a59a4f3c0e739c726884c535d2fc7ada Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:01:24 -0700 Subject: [PATCH 17/23] add `ExtrinsicResponse.extrinsic_receipt` --- bittensor/core/async_subtensor.py | 1 + bittensor/core/subtensor.py | 1 + bittensor/core/types.py | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ab5a764781..3c3782f450 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4220,6 +4220,7 @@ async def sign_and_send_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) + extrinsic_response.extrinsic_receipt = response # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: extrinsic_response.message = ( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b251d4aeaf..37cd6ccbfa 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3111,6 +3111,7 @@ def sign_and_send_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) + extrinsic_response.extrinsic_receipt = response # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: extrinsic_response.message = ( diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 543ee8eb64..73d962489b 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -23,6 +23,8 @@ from bittensor_wallet import Wallet from bittensor.utils.balance import Balance from scalecodec.types import GenericExtrinsic + from async_substrate_interface.sync_substrate import ExtrinsicReceipt + from async_substrate_interface.async_substrate import AsyncExtrinsicReceipt # Type annotations for UIDs and weights. @@ -302,6 +304,7 @@ class ExtrinsicResponse: extrinsic_function: The SDK extrinsic or external function name that was executed (e.g., "add_stake_extrinsic"). extrinsic: The raw extrinsic object used in the call, if available. extrinsic_fee: The fee charged by the extrinsic, if available. + extrinsic_receipt: The receipt object of the submitted extrinsic. transaction_fee: The fee charged by the transaction (e.g., fee for add_stake or transfer_stake), if available. error: Captures the underlying exception if the extrinsic failed, otherwise `None`. data: Arbitrary data returned from the extrinsic, such as decoded events, balance or another extra context. @@ -330,6 +333,7 @@ class ExtrinsicResponse: extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... extrinsic_fee: τ1.0 + extrinsic_receipt: Extrinsic Receipt data transaction_fee: τ1.0 error: None data: None @@ -350,6 +354,9 @@ class ExtrinsicResponse: extrinsic_function: Optional[str] = None extrinsic: Optional["GenericExtrinsic"] = None extrinsic_fee: Optional["Balance"] = None + extrinsic_receipt: Optional[Union["AsyncExtrinsicReceipt", "ExtrinsicReceipt"]] = ( + None + ) transaction_fee: Optional["Balance"] = None error: Optional[Exception] = None data: Optional[Any] = None @@ -366,6 +373,7 @@ def __str__(self): f"\textrinsic_function: {self.extrinsic_function}\n" f"\textrinsic: {self.extrinsic}\n" f"\textrinsic_fee: {self.extrinsic_fee}\n" + f"\textrinsic_receipt: ExtrinsicReceipt\n" f"\ttransaction_fee: {self.transaction_fee}\n" f"\tdata: {self.data}\n" f"\terror: {self.error}" @@ -385,6 +393,7 @@ def as_dict(self) -> dict: "transaction_fee": str(self.transaction_fee) if self.transaction_fee else None, + "extrinsic_receipt": self.extrinsic_receipt, "error": str(self.error) if self.error else None, "data": self.data, } @@ -400,6 +409,7 @@ def __eq__(self, other: Any) -> bool: and self.extrinsic == other.extrinsic and self.extrinsic_fee == other.extrinsic_fee and self.transaction_fee == other.transaction_fee + and self.extrinsic_receipt == other.extrinsic_receipt and self.error == other.error and self.data == other.data ) From 37a6ec62be6b0974b2b16201e83d02af0a189df8 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:10:19 -0700 Subject: [PATCH 18/23] check GPG --- bittensor/core/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 73d962489b..8204e00e58 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -333,7 +333,7 @@ class ExtrinsicResponse: extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... extrinsic_fee: τ1.0 - extrinsic_receipt: Extrinsic Receipt data + extrinsic_receipt: Extrinsic Receipt data of of the submitted extrinsic transaction_fee: τ1.0 error: None data: None From 527f833cc4382c4a457f50e97d5a1d2c4db6f0d0 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:44:32 -0700 Subject: [PATCH 19/23] fix field representation --- bittensor/core/types.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 8204e00e58..74e5d54787 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -366,6 +366,11 @@ def __iter__(self): yield self.message def __str__(self): + _extrinsic_receipt = ( + f"ExtrinsicReceipt\n" + if self.extrinsic_receipt + else f"{self.extrinsic_receipt}\n" + ) return ( f"{self.__class__.__name__}:\n" f"\tsuccess: {self.success}\n" @@ -373,7 +378,7 @@ def __str__(self): f"\textrinsic_function: {self.extrinsic_function}\n" f"\textrinsic: {self.extrinsic}\n" f"\textrinsic_fee: {self.extrinsic_fee}\n" - f"\textrinsic_receipt: ExtrinsicReceipt\n" + f"\textrinsic_receipt: {_extrinsic_receipt}" f"\ttransaction_fee: {self.transaction_fee}\n" f"\tdata: {self.data}\n" f"\terror: {self.error}" From 65dcdbd3df20e7b47ae53b7985cc15d5a121d0dc Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:52:01 -0700 Subject: [PATCH 20/23] uncomment tests --- tests/e2e_tests/test_staking.py | 3344 +++++++++++++++---------------- 1 file changed, 1672 insertions(+), 1672 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index fe30c628ac..cd739fa461 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,636 +18,636 @@ from tests.helpers.helpers import CloseInValue -# def test_single_operation(subtensor, alice_wallet, bob_wallet): -# """ -# Tests: -# - Staking using `add_stake` -# - Unstaking using `unstake` -# - Checks StakeInfo -# """ -# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 -# -# # Register root as Alice - the subnet owner and validator -# assert subtensor.subnets.register_subnet(alice_wallet).success -# -# # Verify subnet created successfully -# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) -# -# assert subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# assert stake == Balance(0).set_unit(alice_subnet_netuid) -# -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1), -# period=16, -# ).success -# -# stake_alice = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# logging.console.info(f"Alice stake: {stake_alice}") -# -# stake_bob = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# logging.console.info(f"Bob stake: {stake_bob}") -# assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) -# -# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) -# -# expected_stakes = [ -# StakeInfo( -# hotkey_ss58=stakes[0].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# ] -# -# fast_blocks_stake = ( -# [ -# StakeInfo( -# hotkey_ss58=stakes[1].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance( -# stakes[1].emission.rao, alice_subnet_netuid -# ), -# drain=0, -# is_registered=True, -# ) -# ] -# if subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# expected_stakes += fast_blocks_stake -# -# assert stakes == expected_stakes -# assert ( -# subtensor.staking.get_stake_for_coldkey -# == subtensor.staking.get_stake_info_for_coldkey -# ) -# -# stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.hotkey.ss58_address, -# ) -# -# assert stakes == { -# 0: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=0, -# stake=Balance(0), -# locked=Balance(0), -# emission=Balance(0), -# drain=0, -# is_registered=False, -# ), -# 1: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=1, -# stake=stake.set_unit(1), -# locked=Balance.from_tao(0, netuid=1), -# emission=Balance.from_tao(0, netuid=1), -# drain=0, -# is_registered=False, -# ), -# 2: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), -# locked=Balance.from_tao(0, netuid=alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# } -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# logging.console.info(f"Alice stake before unstake: {stake}") -# -# # unstale all to check in later -# response = subtensor.staking.unstake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=stake, -# period=16, -# ) -# -# assert response.success is True -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# # all balances have been unstaked -# assert stake == Balance(0).set_unit(alice_subnet_netuid) -# -# -# @pytest.mark.asyncio -# async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet): -# """ -# Async ests: -# - Staking using `add_stake` -# - Unstaking using `unstake` -# - Checks StakeInfo -# """ -# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 -# -# # Register root as Alice - the subnet owner and validator -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# -# # Verify subnet created successfully -# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert await async_wait_to_start_call( -# async_subtensor, alice_wallet, alice_subnet_netuid -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") -# -# stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# assert stake == Balance(0).set_unit(alice_subnet_netuid) -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1), -# period=16, -# ) -# ).success -# -# stake_alice = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# logging.console.info(f"Alice stake: {stake_alice}") -# -# stake_bob = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# logging.console.info(f"Bob stake: {stake_bob}") -# assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) -# -# stakes = await async_subtensor.staking.get_stake_for_coldkey( -# alice_wallet.coldkey.ss58_address -# ) -# -# expected_stakes = [ -# StakeInfo( -# hotkey_ss58=stakes[0].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# ] -# -# fast_blocks_stake = ( -# [ -# StakeInfo( -# hotkey_ss58=stakes[1].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance( -# stakes[1].emission.rao, alice_subnet_netuid -# ), -# drain=0, -# is_registered=True, -# ) -# ] -# if await async_subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# expected_stakes += fast_blocks_stake -# -# assert stakes == expected_stakes -# assert ( -# async_subtensor.staking.get_stake_for_coldkey -# == async_subtensor.staking.get_stake_info_for_coldkey -# ) -# -# stakes = await async_subtensor.staking.get_stake_for_coldkey_and_hotkey( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.hotkey.ss58_address, -# ) -# -# assert stakes == { -# 0: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=0, -# stake=Balance(0), -# locked=Balance(0), -# emission=Balance(0), -# drain=0, -# is_registered=False, -# ), -# 1: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=1, -# stake=stake.set_unit(1), -# locked=Balance.from_tao(0, netuid=1), -# emission=Balance.from_tao(0, netuid=1), -# drain=0, -# is_registered=False, -# ), -# 2: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), -# locked=Balance.from_tao(0, netuid=alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# } -# -# stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# logging.console.info(f"Alice stake before unstake: {stake}") -# -# # unstale all to check in later -# success, message = await async_subtensor.staking.unstake_all( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# period=16, -# ) -# assert success is True, message -# -# stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# logging.console.info(f"Alice stake after unstake: {stake}") -# -# # all balances have been unstaked -# assert stake == Balance(0).set_unit(alice_subnet_netuid) -# -# -# def test_batch_operations(subtensor, alice_wallet, bob_wallet): -# """ -# Tests: -# - Staking using `add_stake_multiple` -# - Unstaking using `unstake_multiple` -# - Checks StakeInfo -# - Checks Accounts Balance -# """ -# netuids = [ -# 2, -# 3, -# ] -# -# for _ in netuids: -# assert subtensor.subnets.register_subnet(alice_wallet).success -# -# # make sure we passed start_call limit for both subnets -# for netuid in netuids: -# assert wait_to_start_call(subtensor, alice_wallet, netuid) -# -# for netuid in netuids: -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=netuid, -# ).success -# -# for netuid in netuids: -# stake = subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# -# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" -# -# balances = subtensor.wallets.get_balances( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.coldkey.ss58_address, -# ) -# -# expected_balances = { -# alice_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[alice_wallet.coldkey.ss58_address].rao -# ), -# bob_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[bob_wallet.coldkey.ss58_address].rao -# ), -# } -# -# assert balances == expected_balances -# -# alice_balance = balances[alice_wallet.coldkey.ss58_address] -# -# assert subtensor.staking.add_stake_multiple( -# wallet=alice_wallet, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# netuids=netuids, -# amounts=[Balance.from_tao(10_000) for _ in netuids], -# ).success -# -# stakes = [ -# subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# for netuid in netuids -# ] -# -# for netuid, stake in zip(netuids, stakes): -# assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" -# -# alice_balance -= len(netuids) * Balance.from_tao(10_000) -# -# balances = subtensor.wallets.get_balances( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.coldkey.ss58_address, -# ) -# -# expected_balances = { -# alice_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[alice_wallet.coldkey.ss58_address].rao -# ), -# bob_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[bob_wallet.coldkey.ss58_address].rao -# ), -# } -# -# assert balances == expected_balances -# -# expected_fee_paid = Balance(0) -# for netuid in netuids: -# call = subtensor.substrate.compose_call( -# call_module="SubtensorModule", -# call_function="remove_stake", -# call_params={ -# "hotkey": bob_wallet.hotkey.ss58_address, -# "amount_unstaked": Balance.from_tao(100).rao, -# "netuid": netuid, -# }, -# ) -# payment_info = subtensor.substrate.get_payment_info( -# call, alice_wallet.coldkeypub -# ) -# fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) -# dynamic_info = subtensor.subnets.subnet(netuid) -# fee_tao = dynamic_info.alpha_to_tao(fee_alpha) -# expected_fee_paid += fee_tao -# -# response = subtensor.staking.unstake_multiple( -# wallet=alice_wallet, -# netuids=netuids, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# amounts=[Balance.from_tao(100) for _ in netuids], -# raise_error=True, -# ) -# logging.console.info(f">>> res {response}") -# assert response.success, response.message -# -# for netuid, old_stake in zip(netuids, stakes): -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# -# assert stake < old_stake, f"netuid={netuid} stake={stake}" -# -# balances = subtensor.wallets.get_balances( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.coldkey.ss58_address, -# ) -# -# assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees -# balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) -# ) == Balance.from_tao(999_999.7994) -# -# assert balances[alice_wallet.coldkey.ss58_address] > alice_balance -# -# -# @pytest.mark.asyncio -# async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): -# """ -# Async tests: -# - Staking using `add_stake_multiple` -# - Unstaking using `unstake_multiple` -# - Checks StakeInfo -# - Checks Accounts Balance -# """ -# netuids = [ -# 2, -# 3, -# ] -# -# for _ in netuids: -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# -# # make sure we passed start_call limit for both subnets -# for netuid in netuids: -# assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) -# -# for netuid in netuids: -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=netuid, -# ) -# ).success -# -# for netuid in netuids: -# stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# -# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" -# -# balances = await async_subtensor.wallets.get_balances( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.coldkey.ss58_address, -# ) -# -# expected_balances = { -# alice_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[alice_wallet.coldkey.ss58_address].rao -# ), -# bob_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[bob_wallet.coldkey.ss58_address].rao -# ), -# } -# -# assert balances == expected_balances -# -# alice_balance = balances[alice_wallet.coldkey.ss58_address] -# -# response = await async_subtensor.staking.add_stake_multiple( -# wallet=alice_wallet, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# netuids=netuids, -# amounts=[Balance.from_tao(10_000) for _ in netuids], -# ) -# assert response.success -# -# stakes = [ -# await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# for netuid in netuids -# ] -# -# for netuid, stake in zip(netuids, stakes): -# assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" -# -# alice_balance -= len(netuids) * Balance.from_tao(10_000) -# -# balances = await async_subtensor.wallets.get_balances( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.coldkey.ss58_address, -# ) -# -# expected_balances = { -# alice_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[alice_wallet.coldkey.ss58_address].rao -# ), -# bob_wallet.coldkey.ss58_address: get_dynamic_balance( -# balances[bob_wallet.coldkey.ss58_address].rao -# ), -# } -# -# assert balances == expected_balances -# -# expected_fee_paid = Balance(0) -# for netuid in netuids: -# call = await async_subtensor.substrate.compose_call( -# call_module="SubtensorModule", -# call_function="remove_stake", -# call_params={ -# "hotkey": bob_wallet.hotkey.ss58_address, -# "amount_unstaked": Balance.from_tao(100).rao, -# "netuid": netuid, -# }, -# ) -# payment_info = await async_subtensor.substrate.get_payment_info( -# call, alice_wallet.coldkeypub -# ) -# fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) -# dynamic_info = await async_subtensor.subnets.subnet(netuid) -# fee_tao = dynamic_info.alpha_to_tao(fee_alpha) -# expected_fee_paid += fee_tao -# -# response = await async_subtensor.staking.unstake_multiple( -# wallet=alice_wallet, -# netuids=netuids, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# amounts=[Balance.from_tao(100) for _ in netuids], -# ) -# assert response.success, response.message -# -# for netuid, old_stake in zip(netuids, stakes): -# stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# -# assert stake < old_stake, f"netuid={netuid} stake={stake}" -# -# balances = await async_subtensor.wallets.get_balances( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.coldkey.ss58_address, -# ) -# -# assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees -# balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) -# ) == Balance.from_tao(999_999.7994) -# -# assert balances[alice_wallet.coldkey.ss58_address] > alice_balance +def test_single_operation(subtensor, alice_wallet, bob_wallet): + """ + Tests: + - Staking using `add_stake` + - Unstaking using `unstake` + - Checks StakeInfo + """ + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + + # Register root as Alice - the subnet owner and validator + assert subtensor.subnets.register_subnet(alice_wallet).success + + # Verify subnet created successfully + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + + assert subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ).success + + stake_alice = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake: {stake_alice}") + + stake_bob = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + logging.console.info(f"Bob stake: {stake_bob}") + assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) + + stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + + expected_stakes = [ + StakeInfo( + hotkey_ss58=stakes[0].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + + fast_blocks_stake = ( + [ + StakeInfo( + hotkey_ss58=stakes[1].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + stakes[1].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ) + ] + if subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_blocks_stake + + assert stakes == expected_stakes + assert ( + subtensor.staking.get_stake_for_coldkey + == subtensor.staking.get_stake_info_for_coldkey + ) + + stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + ) + + assert stakes == { + 0: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=0, + stake=Balance(0), + locked=Balance(0), + emission=Balance(0), + drain=0, + is_registered=False, + ), + 1: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=1, + stake=stake.set_unit(1), + locked=Balance.from_tao(0, netuid=1), + emission=Balance.from_tao(0, netuid=1), + drain=0, + is_registered=False, + ), + 2: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), + locked=Balance.from_tao(0, netuid=alice_subnet_netuid), + emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + } + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake before unstake: {stake}") + + # unstale all to check in later + response = subtensor.staking.unstake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake, + period=16, + ) + + assert response.success is True + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + # all balances have been unstaked + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + +@pytest.mark.asyncio +async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet): + """ + Async ests: + - Staking using `add_stake` + - Unstaking using `unstake` + - Checks StakeInfo + """ + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Register root as Alice - the subnet owner and validator + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ) + ).success + logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success + logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ) + ).success + + stake_alice = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake: {stake_alice}") + + stake_bob = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + logging.console.info(f"Bob stake: {stake_bob}") + assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) + + stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + expected_stakes = [ + StakeInfo( + hotkey_ss58=stakes[0].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + + fast_blocks_stake = ( + [ + StakeInfo( + hotkey_ss58=stakes[1].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + stakes[1].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ) + ] + if await async_subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_blocks_stake + + assert stakes == expected_stakes + assert ( + async_subtensor.staking.get_stake_for_coldkey + == async_subtensor.staking.get_stake_info_for_coldkey + ) + + stakes = await async_subtensor.staking.get_stake_for_coldkey_and_hotkey( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + ) + + assert stakes == { + 0: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=0, + stake=Balance(0), + locked=Balance(0), + emission=Balance(0), + drain=0, + is_registered=False, + ), + 1: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=1, + stake=stake.set_unit(1), + locked=Balance.from_tao(0, netuid=1), + emission=Balance.from_tao(0, netuid=1), + drain=0, + is_registered=False, + ), + 2: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), + locked=Balance.from_tao(0, netuid=alice_subnet_netuid), + emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + } + + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake before unstake: {stake}") + + # unstale all to check in later + success, message = await async_subtensor.staking.unstake_all( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + period=16, + ) + assert success is True, message + + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake after unstake: {stake}") + + # all balances have been unstaked + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + +def test_batch_operations(subtensor, alice_wallet, bob_wallet): + """ + Tests: + - Staking using `add_stake_multiple` + - Unstaking using `unstake_multiple` + - Checks StakeInfo + - Checks Accounts Balance + """ + netuids = [ + 2, + 3, + ] + + for _ in netuids: + assert subtensor.subnets.register_subnet(alice_wallet).success + + # make sure we passed start_call limit for both subnets + for netuid in netuids: + assert wait_to_start_call(subtensor, alice_wallet, netuid) + + for netuid in netuids: + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=netuid, + ).success + + for netuid in netuids: + stake = subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + + assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + + balances = subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + expected_balances = { + alice_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[alice_wallet.coldkey.ss58_address].rao + ), + bob_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[bob_wallet.coldkey.ss58_address].rao + ), + } + + assert balances == expected_balances + + alice_balance = balances[alice_wallet.coldkey.ss58_address] + + assert subtensor.staking.add_stake_multiple( + wallet=alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(10_000) for _ in netuids], + ).success + + stakes = [ + subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + for netuid in netuids + ] + + for netuid, stake in zip(netuids, stakes): + assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + + alice_balance -= len(netuids) * Balance.from_tao(10_000) + + balances = subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + expected_balances = { + alice_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[alice_wallet.coldkey.ss58_address].rao + ), + bob_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[bob_wallet.coldkey.ss58_address].rao + ), + } + + assert balances == expected_balances + + expected_fee_paid = Balance(0) + for netuid in netuids: + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": bob_wallet.hotkey.ss58_address, + "amount_unstaked": Balance.from_tao(100).rao, + "netuid": netuid, + }, + ) + payment_info = subtensor.substrate.get_payment_info( + call, alice_wallet.coldkeypub + ) + fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) + dynamic_info = subtensor.subnets.subnet(netuid) + fee_tao = dynamic_info.alpha_to_tao(fee_alpha) + expected_fee_paid += fee_tao + + response = subtensor.staking.unstake_multiple( + wallet=alice_wallet, + netuids=netuids, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + amounts=[Balance.from_tao(100) for _ in netuids], + raise_error=True, + ) + logging.console.info(f">>> res {response}") + assert response.success, response.message + + for netuid, old_stake in zip(netuids, stakes): + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + + assert stake < old_stake, f"netuid={netuid} stake={stake}" + + balances = subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees + balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) + ) == Balance.from_tao(999_999.7994) + + assert balances[alice_wallet.coldkey.ss58_address] > alice_balance + + +@pytest.mark.asyncio +async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): + """ + Async tests: + - Staking using `add_stake_multiple` + - Unstaking using `unstake_multiple` + - Checks StakeInfo + - Checks Accounts Balance + """ + netuids = [ + 2, + 3, + ] + + for _ in netuids: + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + + # make sure we passed start_call limit for both subnets + for netuid in netuids: + assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) + + for netuid in netuids: + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=netuid, + ) + ).success + + for netuid in netuids: + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + + assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + + balances = await async_subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + expected_balances = { + alice_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[alice_wallet.coldkey.ss58_address].rao + ), + bob_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[bob_wallet.coldkey.ss58_address].rao + ), + } + + assert balances == expected_balances + + alice_balance = balances[alice_wallet.coldkey.ss58_address] + + response = await async_subtensor.staking.add_stake_multiple( + wallet=alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(10_000) for _ in netuids], + ) + assert response.success + + stakes = [ + await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + for netuid in netuids + ] + + for netuid, stake in zip(netuids, stakes): + assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + + alice_balance -= len(netuids) * Balance.from_tao(10_000) + + balances = await async_subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + expected_balances = { + alice_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[alice_wallet.coldkey.ss58_address].rao + ), + bob_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[bob_wallet.coldkey.ss58_address].rao + ), + } + + assert balances == expected_balances + + expected_fee_paid = Balance(0) + for netuid in netuids: + call = await async_subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": bob_wallet.hotkey.ss58_address, + "amount_unstaked": Balance.from_tao(100).rao, + "netuid": netuid, + }, + ) + payment_info = await async_subtensor.substrate.get_payment_info( + call, alice_wallet.coldkeypub + ) + fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) + dynamic_info = await async_subtensor.subnets.subnet(netuid) + fee_tao = dynamic_info.alpha_to_tao(fee_alpha) + expected_fee_paid += fee_tao + + response = await async_subtensor.staking.unstake_multiple( + wallet=alice_wallet, + netuids=netuids, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + amounts=[Balance.from_tao(100) for _ in netuids], + ) + assert response.success, response.message + + for netuid, old_stake in zip(netuids, stakes): + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + + assert stake < old_stake, f"netuid={netuid} stake={stake}" + + balances = await async_subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees + balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) + ) == Balance.from_tao(999_999.7994) + + assert balances[alice_wallet.coldkey.ss58_address] > alice_balance def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet): @@ -1046,1045 +1046,1045 @@ async def test_safe_staking_scenarios_async( assert response.success is True, "Unstake should succeed" -# def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): -# """ -# Tests safe swap stake scenarios with different parameters. -# -# Tests: -# 1. Fails with strict threshold (0.5%) -# 2. Succeeds with lenient threshold (10%) -# """ -# # Create new subnet (netuid 2) and register Alice -# origin_netuid = 2 -# assert subtensor.subnets.register_subnet(bob_wallet).success -# assert subtensor.subnets.subnet_exists(origin_netuid), ( -# "Subnet wasn't created successfully" -# ) -# dest_netuid = 3 -# assert subtensor.subnets.register_subnet(bob_wallet).success -# assert subtensor.subnets.subnet_exists(dest_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# # make sure we passed start_call limit for both subnets -# assert wait_to_start_call(subtensor, bob_wallet, origin_netuid) -# assert wait_to_start_call(subtensor, bob_wallet, dest_netuid) -# -# # Register Alice on both subnets -# assert subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=origin_netuid, -# ).success -# assert subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=dest_netuid, -# ).success -# -# # Add initial stake to swap from -# initial_stake_amount = Balance.from_tao(10_000) -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=origin_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=initial_stake_amount, -# ).success -# -# origin_stake = subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# alice_wallet.hotkey.ss58_address, -# netuid=origin_netuid, -# ) -# assert origin_stake > Balance(0).set_unit(origin_netuid), ( -# "Origin stake should be non-zero" -# ) -# -# stake_swap_amount = Balance.from_tao(10_000) -# # 1. Try swap with strict threshold and big amount- should fail -# response = subtensor.staking.swap_stake( -# wallet=alice_wallet, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=origin_netuid, -# destination_netuid=dest_netuid, -# amount=stake_swap_amount, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# safe_swapping=True, -# rate_tolerance=0.005, # 0.5% -# allow_partial_stake=False, -# ) -# assert response.success is False -# -# # Verify no stake was moved -# dest_stake = subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# alice_wallet.hotkey.ss58_address, -# netuid=dest_netuid, -# ) -# assert dest_stake == Balance(0).set_unit(dest_netuid), ( -# "Destination stake should remain 0 after failed swap" -# ) -# -# # 2. Try swap with higher threshold and less amount - should succeed -# stake_swap_amount = Balance.from_tao(100) -# response = subtensor.staking.swap_stake( -# wallet=alice_wallet, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=origin_netuid, -# destination_netuid=dest_netuid, -# amount=stake_swap_amount, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# safe_swapping=True, -# rate_tolerance=0.3, # 30% -# allow_partial_stake=True, -# ) -# assert response.success is True -# -# # Verify stake was moved -# dest_stake = subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# alice_wallet.hotkey.ss58_address, -# netuid=dest_netuid, -# ) -# assert dest_stake > Balance(0).set_unit(dest_netuid), ( -# "Destination stake should be non-zero after successful swap" -# ) -# -# -# @pytest.mark.asyncio -# async def test_safe_swap_stake_scenarios_async( -# async_subtensor, alice_wallet, bob_wallet -# ): -# """ -# Tests safe swap stake scenarios with different parameters. -# -# Tests: -# 1. Fails with strict threshold (0.5%) -# 2. Succeeds with lenient threshold (10%) -# """ -# # Create new subnet (netuid 2) and register Alice -# origin_netuid = 2 -# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(origin_netuid), ( -# "Subnet wasn't created successfully" -# ) -# dest_netuid = 3 -# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(dest_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# # make sure we passed start_call limit for both subnets -# assert await async_wait_to_start_call(async_subtensor, bob_wallet, origin_netuid) -# assert await async_wait_to_start_call(async_subtensor, bob_wallet, dest_netuid) -# -# # Register Alice on both subnets -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=origin_netuid, -# ) -# ).success -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=dest_netuid, -# ) -# ).success -# -# # Add initial stake to swap from -# initial_stake_amount = Balance.from_tao(10_000) -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=origin_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=initial_stake_amount, -# ) -# ).success -# -# origin_stake = await async_subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# alice_wallet.hotkey.ss58_address, -# netuid=origin_netuid, -# ) -# assert origin_stake > Balance(0).set_unit(origin_netuid), ( -# "Origin stake should be non-zero" -# ) -# -# stake_swap_amount = Balance.from_tao(10_000) -# # 1. Try swap with strict threshold and big amount- should fail -# response = await async_subtensor.staking.swap_stake( -# wallet=alice_wallet, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=origin_netuid, -# destination_netuid=dest_netuid, -# amount=stake_swap_amount, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# safe_swapping=True, -# rate_tolerance=0.005, # 0.5% -# allow_partial_stake=False, -# ) -# assert response.success is False -# -# # Verify no stake was moved -# dest_stake = await async_subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# alice_wallet.hotkey.ss58_address, -# netuid=dest_netuid, -# ) -# assert dest_stake == Balance(0).set_unit(dest_netuid), ( -# "Destination stake should remain 0 after failed swap" -# ) -# -# # 2. Try swap with higher threshold and less amount - should succeed -# stake_swap_amount = Balance.from_tao(100) -# response = await async_subtensor.staking.swap_stake( -# wallet=alice_wallet, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=origin_netuid, -# destination_netuid=dest_netuid, -# amount=stake_swap_amount, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# safe_swapping=True, -# rate_tolerance=0.3, # 30% -# allow_partial_stake=True, -# ) -# assert response.success is True -# -# # Verify stake was moved -# dest_stake = await async_subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# alice_wallet.hotkey.ss58_address, -# netuid=dest_netuid, -# ) -# assert dest_stake > Balance(0).set_unit(dest_netuid), ( -# "Destination stake should be non-zero after successful swap" -# ) -# -# -# def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): -# """ -# Tests: -# - Adding stake -# - Moving stake from one hotkey-subnet pair to another -# - Testing `move_stake` method with `move_all_stake=True` flag. -# """ -# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 -# assert subtensor.subnets.register_subnet(alice_wallet).success -# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) -# -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1_000), -# ).success -# -# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) -# -# assert stakes == [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# ] -# -# bob_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 -# assert subtensor.subnets.register_subnet(bob_wallet).success -# assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) -# -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ).success -# -# assert subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid, -# ).success -# -# response = subtensor.staking.move_stake( -# wallet=alice_wallet, -# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=alice_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# amount=stakes[0].stake, -# wait_for_finalization=True, -# wait_for_inclusion=True, -# ) -# assert response.success is True -# -# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) -# -# expected_stakes = [ -# StakeInfo( -# hotkey_ss58=stakes[0].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid -# if subtensor.chain.is_fast_blocks() -# else bob_subnet_netuid, -# stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), -# locked=Balance(0).set_unit(bob_subnet_netuid), -# emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), -# drain=0, -# is_registered=True, -# ) -# ] -# -# fast_block_stake = ( -# [ -# StakeInfo( -# hotkey_ss58=stakes[1].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=bob_subnet_netuid, -# stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), -# locked=Balance(0).set_unit(bob_subnet_netuid), -# emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# ] -# if subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# expected_stakes += fast_block_stake -# assert stakes == expected_stakes -# -# # test move_stake with move_all_stake=True -# dave_stake = subtensor.staking.get_stake( -# coldkey_ss58=dave_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# ) -# logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") -# -# assert subtensor.staking.add_stake( -# wallet=dave_wallet, -# netuid=bob_subnet_netuid, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1000), -# allow_partial_stake=True, -# ).success -# -# dave_stake = subtensor.staking.get_stake( -# coldkey_ss58=dave_wallet.coldkey.ss58_address, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# ) -# logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") -# -# # let chain to process the transaction -# subtensor.wait_for_block( -# subtensor.block + subtensor.subnets.tempo(netuid=bob_subnet_netuid) -# ) -# -# response = subtensor.staking.move_stake( -# wallet=dave_wallet, -# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, -# origin_netuid=bob_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# move_all_stake=True, -# ) -# assert response.success is True -# -# dave_stake = subtensor.staking.get_stake( -# coldkey_ss58=dave_wallet.coldkey.ss58_address, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# ) -# logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") -# -# assert dave_stake.rao == CloseInValue(0, 0.00001) -# -# -# @pytest.mark.asyncio -# async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): -# """ -# Tests: -# - Adding stake -# - Moving stake from one hotkey-subnet pair to another -# - Testing `move_stake` method with `move_all_stake=True` flag. -# """ -# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert await async_wait_to_start_call( -# async_subtensor, alice_wallet, alice_subnet_netuid -# ) -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1_000), -# ) -# ).success -# -# stakes = await async_subtensor.staking.get_stake_for_coldkey( -# alice_wallet.coldkey.ss58_address -# ) -# -# assert stakes == [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# ] -# -# bob_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 -# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert await async_wait_to_start_call( -# async_subtensor, bob_wallet, bob_subnet_netuid -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# -# response = await async_subtensor.staking.move_stake( -# wallet=alice_wallet, -# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=alice_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# amount=stakes[0].stake, -# wait_for_finalization=True, -# wait_for_inclusion=True, -# ) -# assert response.success is True -# -# stakes = await async_subtensor.staking.get_stake_for_coldkey( -# alice_wallet.coldkey.ss58_address -# ) -# -# expected_stakes = [ -# StakeInfo( -# hotkey_ss58=stakes[0].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid -# if await async_subtensor.chain.is_fast_blocks() -# else bob_subnet_netuid, -# stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), -# locked=Balance(0).set_unit(bob_subnet_netuid), -# emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), -# drain=0, -# is_registered=True, -# ) -# ] -# -# fast_block_stake = ( -# [ -# StakeInfo( -# hotkey_ss58=stakes[1].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=bob_subnet_netuid, -# stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), -# locked=Balance(0).set_unit(bob_subnet_netuid), -# emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# ] -# if await async_subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# expected_stakes += fast_block_stake -# assert stakes == expected_stakes -# -# # test move_stake with move_all_stake=True -# dave_stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=dave_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# ) -# logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=dave_wallet, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# amount=Balance.from_tao(1000), -# allow_partial_stake=True, -# ) -# ).success -# -# dave_stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=dave_wallet.coldkey.ss58_address, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# ) -# logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") -# -# block_, tampo_ = await asyncio.gather( -# async_subtensor.block, async_subtensor.subnets.tempo(netuid=bob_subnet_netuid) -# ) -# # let chain to process the transaction -# await async_subtensor.wait_for_block(block_ + tampo_) -# -# response = await async_subtensor.staking.move_stake( -# wallet=dave_wallet, -# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, -# origin_netuid=bob_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# move_all_stake=True, -# ) -# assert response.success is True -# -# dave_stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=dave_wallet.coldkey.ss58_address, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# ) -# logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") -# -# assert dave_stake.rao == CloseInValue(0, 0.00001) -# -# -# def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): -# """ -# Tests: -# - Adding stake -# - Transferring stake from one coldkey-subnet pair to another -# """ -# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 -# -# assert subtensor.subnets.register_subnet(alice_wallet).success -# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) -# -# assert subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ).success -# -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1_000), -# ).success -# -# alice_stakes = subtensor.staking.get_stake_for_coldkey( -# alice_wallet.coldkey.ss58_address -# ) -# -# assert alice_stakes == [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance( -# alice_stakes[0].emission.rao, alice_subnet_netuid -# ), -# drain=0, -# is_registered=True, -# ), -# ] -# -# bob_stakes = subtensor.staking.get_stake_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# -# assert bob_stakes == [] -# -# dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 -# assert subtensor.subnets.register_subnet(dave_wallet).success -# -# assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) -# -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=dave_subnet_netuid, -# ).success -# -# response = subtensor.staking.transfer_stake( -# alice_wallet, -# destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=alice_subnet_netuid, -# destination_netuid=dave_subnet_netuid, -# amount=alice_stakes[0].stake, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# ) -# assert response.success is True -# -# alice_stakes = subtensor.staking.get_stake_for_coldkey( -# alice_wallet.coldkey.ss58_address -# ) -# -# expected_alice_stake = ( -# [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance( -# alice_stakes[0].stake.rao, alice_subnet_netuid -# ), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance( -# alice_stakes[0].emission.rao, alice_subnet_netuid -# ), -# drain=0, -# is_registered=True, -# ), -# ] -# if subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# assert alice_stakes == expected_alice_stake -# -# bob_stakes = subtensor.staking.get_stake_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# -# expected_bob_stake = [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=bob_wallet.coldkey.ss58_address, -# netuid=dave_subnet_netuid, -# stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), -# locked=Balance(0).set_unit(dave_subnet_netuid), -# emission=get_dynamic_balance( -# bob_stakes[0].emission.rao, dave_subnet_netuid -# ), -# drain=0, -# is_registered=False, -# ), -# ] -# assert bob_stakes == expected_bob_stake -# -# -# @pytest.mark.asyncio -# async def test_transfer_stake_async( -# async_subtensor, alice_wallet, bob_wallet, dave_wallet -# ): -# """ -# Tests: -# - Adding stake -# - Transferring stake from one coldkey-subnet pair to another -# """ -# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 -# -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert await async_wait_to_start_call( -# async_subtensor, alice_wallet, alice_subnet_netuid -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1_000), -# ) -# ).success -# -# alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( -# alice_wallet.coldkey.ss58_address -# ) -# -# assert alice_stakes == [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance( -# alice_stakes[0].emission.rao, alice_subnet_netuid -# ), -# drain=0, -# is_registered=True, -# ), -# ] -# -# bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# -# assert bob_stakes == [] -# -# dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 -# assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success -# -# assert await async_wait_to_start_call( -# async_subtensor, dave_wallet, dave_subnet_netuid -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=dave_subnet_netuid, -# ) -# ).success -# -# response = await async_subtensor.staking.transfer_stake( -# alice_wallet, -# destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=alice_subnet_netuid, -# destination_netuid=dave_subnet_netuid, -# amount=alice_stakes[0].stake, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# ) -# assert response.success is True -# -# alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( -# alice_wallet.coldkey.ss58_address -# ) -# -# expected_alice_stake = ( -# [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance( -# alice_stakes[0].stake.rao, alice_subnet_netuid -# ), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance( -# alice_stakes[0].emission.rao, alice_subnet_netuid -# ), -# drain=0, -# is_registered=True, -# ), -# ] -# if await async_subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# assert alice_stakes == expected_alice_stake -# -# bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# -# expected_bob_stake = [ -# StakeInfo( -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# coldkey_ss58=bob_wallet.coldkey.ss58_address, -# netuid=dave_subnet_netuid, -# stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), -# locked=Balance(0).set_unit(dave_subnet_netuid), -# emission=get_dynamic_balance( -# bob_stakes[0].emission.rao, dave_subnet_netuid -# ), -# drain=0, -# is_registered=False, -# ), -# ] -# assert bob_stakes == expected_bob_stake -# -# -# # For test we set rate_tolerance=0.7 (70%) because of price is highly dynamic for fast-blocks and 2 SN to avoid ` -# # Slippage is too high for the transaction`. This logic controls by the chain. -# # Also this test implementation works with non-fast-blocks run. -# @pytest.mark.parametrize( -# "rate_tolerance", -# [None, 1.0], -# ids=[ -# "Without price limit", -# "With price limit", -# ], -# ) -# def test_unstaking_with_limit( -# subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance -# ): -# """Test unstaking with limits goes well for all subnets with and without price limit.""" -# # Register first SN -# alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 -# assert subtensor.subnets.register_subnet(alice_wallet).success -# assert subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( -# "Subnet wasn't created successfully" -# ) -# -# wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_2) -# -# # Register Bob and Dave in SN2 -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_2, -# ).success -# -# assert subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_2, -# ).success -# -# # Register second SN -# alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 -# assert subtensor.subnets.register_subnet(alice_wallet).success -# assert subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( -# "Subnet wasn't created successfully" -# ) -# -# wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_3) -# -# # Register Bob and Dave in SN3 -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_3, -# ).success -# -# assert subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_3, -# ).success -# -# # Check Bob's stakes are empty. -# assert ( -# subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) -# == [] -# ) -# -# # Bob stakes to Dave in both SNs -# -# assert subtensor.staking.add_stake( -# wallet=bob_wallet, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid_2, -# amount=Balance.from_tao(10000), -# period=16, -# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" -# -# assert subtensor.staking.add_stake( -# wallet=bob_wallet, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid_3, -# amount=Balance.from_tao(15000), -# period=16, -# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" -# -# # Check that both stakes are presented in result -# bob_stakes = subtensor.staking.get_stake_info_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# assert len(bob_stakes) == 2 -# -# if rate_tolerance == 0.0001: -# # Raise the error -# with pytest.raises( -# ChainError, match="Slippage is too high for the transaction" -# ): -# subtensor.staking.unstake_all( -# wallet=bob_wallet, -# netuid=bob_stakes[0].netuid, -# hotkey_ss58=bob_stakes[0].hotkey_ss58, -# rate_tolerance=rate_tolerance, -# ) -# else: -# # Successful cases -# for si in bob_stakes: -# assert subtensor.staking.unstake_all( -# wallet=bob_wallet, -# netuid=si.netuid, -# hotkey_ss58=si.hotkey_ss58, -# rate_tolerance=rate_tolerance, -# ).success -# -# # Make sure both unstake were successful. -# bob_stakes = subtensor.staking.get_stake_info_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# assert len(bob_stakes) == 0 -# -# -# @pytest.mark.parametrize( -# "rate_tolerance", -# [None, 1.0], -# ids=[ -# "Without price limit", -# "With price limit", -# ], -# ) -# @pytest.mark.asyncio -# async def test_unstaking_with_limit_async( -# async_subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance -# ): -# """Test unstaking with limits goes well for all subnets with and without price limit.""" -# # Register first SN -# alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( -# "Subnet wasn't created successfully" -# ) -# -# assert await async_wait_to_start_call( -# async_subtensor, alice_wallet, alice_subnet_netuid_2 -# ) -# -# # Register Bob and Dave in SN2 -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_2, -# ) -# ).success -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_2, -# ) -# ).success -# -# # Register second SN -# alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( -# "Subnet wasn't created successfully" -# ) -# -# await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid_3) -# -# # Register Bob and Dave in SN3 -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_3, -# ) -# ).success -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_3, -# ) -# ).success -# -# # Check Bob's stakes are empty. -# assert ( -# await async_subtensor.staking.get_stake_info_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# == [] -# ) -# -# # Bob stakes to Dave in both SNs -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_2, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(10000), -# period=16, -# ) -# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_3, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(15000), -# period=16, -# ) -# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" -# -# # Check that both stakes are presented in result -# bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# assert len(bob_stakes) == 2 -# -# if rate_tolerance == 0.0001: -# # Raise the error -# with pytest.raises( -# ChainError, match="Slippage is too high for the transaction" -# ): -# await async_subtensor.staking.unstake_all( -# wallet=bob_wallet, -# netuid=bob_stakes[0].netuid, -# hotkey_ss58=bob_stakes[0].hotkey_ss58, -# rate_tolerance=rate_tolerance, -# ) -# else: -# # Successful cases -# for si in bob_stakes: -# assert ( -# await async_subtensor.staking.unstake_all( -# wallet=bob_wallet, -# hotkey_ss58=si.hotkey_ss58, -# netuid=si.netuid, -# rate_tolerance=rate_tolerance, -# ) -# ).success -# -# # Make sure both unstake were successful. -# bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# assert len(bob_stakes) == 0 +def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): + """ + Tests safe swap stake scenarios with different parameters. + + Tests: + 1. Fails with strict threshold (0.5%) + 2. Succeeds with lenient threshold (10%) + """ + # Create new subnet (netuid 2) and register Alice + origin_netuid = 2 + assert subtensor.subnets.register_subnet(bob_wallet).success + assert subtensor.subnets.subnet_exists(origin_netuid), ( + "Subnet wasn't created successfully" + ) + dest_netuid = 3 + assert subtensor.subnets.register_subnet(bob_wallet).success + assert subtensor.subnets.subnet_exists(dest_netuid), ( + "Subnet wasn't created successfully" + ) + + # make sure we passed start_call limit for both subnets + assert wait_to_start_call(subtensor, bob_wallet, origin_netuid) + assert wait_to_start_call(subtensor, bob_wallet, dest_netuid) + + # Register Alice on both subnets + assert subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=origin_netuid, + ).success + assert subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dest_netuid, + ).success + + # Add initial stake to swap from + initial_stake_amount = Balance.from_tao(10_000) + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=origin_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=initial_stake_amount, + ).success + + origin_stake = subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=origin_netuid, + ) + assert origin_stake > Balance(0).set_unit(origin_netuid), ( + "Origin stake should be non-zero" + ) + + stake_swap_amount = Balance.from_tao(10_000) + # 1. Try swap with strict threshold and big amount- should fail + response = subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=dest_netuid, + amount=stake_swap_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_swapping=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert response.success is False + + # Verify no stake was moved + dest_stake = subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=dest_netuid, + ) + assert dest_stake == Balance(0).set_unit(dest_netuid), ( + "Destination stake should remain 0 after failed swap" + ) + + # 2. Try swap with higher threshold and less amount - should succeed + stake_swap_amount = Balance.from_tao(100) + response = subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=dest_netuid, + amount=stake_swap_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_swapping=True, + rate_tolerance=0.3, # 30% + allow_partial_stake=True, + ) + assert response.success is True + + # Verify stake was moved + dest_stake = subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=dest_netuid, + ) + assert dest_stake > Balance(0).set_unit(dest_netuid), ( + "Destination stake should be non-zero after successful swap" + ) + + +@pytest.mark.asyncio +async def test_safe_swap_stake_scenarios_async( + async_subtensor, alice_wallet, bob_wallet +): + """ + Tests safe swap stake scenarios with different parameters. + + Tests: + 1. Fails with strict threshold (0.5%) + 2. Succeeds with lenient threshold (10%) + """ + # Create new subnet (netuid 2) and register Alice + origin_netuid = 2 + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success + assert await async_subtensor.subnets.subnet_exists(origin_netuid), ( + "Subnet wasn't created successfully" + ) + dest_netuid = 3 + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success + assert await async_subtensor.subnets.subnet_exists(dest_netuid), ( + "Subnet wasn't created successfully" + ) + + # make sure we passed start_call limit for both subnets + assert await async_wait_to_start_call(async_subtensor, bob_wallet, origin_netuid) + assert await async_wait_to_start_call(async_subtensor, bob_wallet, dest_netuid) + + # Register Alice on both subnets + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=origin_netuid, + ) + ).success + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dest_netuid, + ) + ).success + + # Add initial stake to swap from + initial_stake_amount = Balance.from_tao(10_000) + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=origin_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=initial_stake_amount, + ) + ).success + + origin_stake = await async_subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=origin_netuid, + ) + assert origin_stake > Balance(0).set_unit(origin_netuid), ( + "Origin stake should be non-zero" + ) + + stake_swap_amount = Balance.from_tao(10_000) + # 1. Try swap with strict threshold and big amount- should fail + response = await async_subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=dest_netuid, + amount=stake_swap_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_swapping=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert response.success is False + + # Verify no stake was moved + dest_stake = await async_subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=dest_netuid, + ) + assert dest_stake == Balance(0).set_unit(dest_netuid), ( + "Destination stake should remain 0 after failed swap" + ) + + # 2. Try swap with higher threshold and less amount - should succeed + stake_swap_amount = Balance.from_tao(100) + response = await async_subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=dest_netuid, + amount=stake_swap_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_swapping=True, + rate_tolerance=0.3, # 30% + allow_partial_stake=True, + ) + assert response.success is True + + # Verify stake was moved + dest_stake = await async_subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=dest_netuid, + ) + assert dest_stake > Balance(0).set_unit(dest_netuid), ( + "Destination stake should be non-zero after successful swap" + ) + + +def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): + """ + Tests: + - Adding stake + - Moving stake from one hotkey-subnet pair to another + - Testing `move_stake` method with `move_all_stake=True` flag. + """ + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet).success + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1_000), + ).success + + stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + + assert stakes == [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + + bob_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 + assert subtensor.subnets.register_subnet(bob_wallet).success + assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) + + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ).success + + assert subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + ).success + + response = subtensor.staking.move_stake( + wallet=alice_wallet, + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + amount=stakes[0].stake, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + assert response.success is True + + stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + + expected_stakes = [ + StakeInfo( + hotkey_ss58=stakes[0].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid + if subtensor.chain.is_fast_blocks() + else bob_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), + locked=Balance(0).set_unit(bob_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), + drain=0, + is_registered=True, + ) + ] + + fast_block_stake = ( + [ + StakeInfo( + hotkey_ss58=stakes[1].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=bob_subnet_netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), + locked=Balance(0).set_unit(bob_subnet_netuid), + emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + if subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_block_stake + assert stakes == expected_stakes + + # test move_stake with move_all_stake=True + dave_stake = subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") + + assert subtensor.staking.add_stake( + wallet=dave_wallet, + netuid=bob_subnet_netuid, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1000), + allow_partial_stake=True, + ).success + + dave_stake = subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") + + # let chain to process the transaction + subtensor.wait_for_block( + subtensor.block + subtensor.subnets.tempo(netuid=bob_subnet_netuid) + ) + + response = subtensor.staking.move_stake( + wallet=dave_wallet, + origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, + origin_netuid=bob_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + move_all_stake=True, + ) + assert response.success is True + + dave_stake = subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") + + assert dave_stake.rao == CloseInValue(0, 0.00001) + + +@pytest.mark.asyncio +async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): + """ + Tests: + - Adding stake + - Moving stake from one hotkey-subnet pair to another + - Testing `move_stake` method with `move_all_stake=True` flag. + """ + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1_000), + ) + ).success + + stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + assert stakes == [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + + bob_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success + assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, bob_wallet, bob_subnet_netuid + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success + + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + ) + ).success + + response = await async_subtensor.staking.move_stake( + wallet=alice_wallet, + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + amount=stakes[0].stake, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + assert response.success is True + + stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + expected_stakes = [ + StakeInfo( + hotkey_ss58=stakes[0].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid + if await async_subtensor.chain.is_fast_blocks() + else bob_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), + locked=Balance(0).set_unit(bob_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), + drain=0, + is_registered=True, + ) + ] + + fast_block_stake = ( + [ + StakeInfo( + hotkey_ss58=stakes[1].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=bob_subnet_netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), + locked=Balance(0).set_unit(bob_subnet_netuid), + emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + if await async_subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_block_stake + assert stakes == expected_stakes + + # test move_stake with move_all_stake=True + dave_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") + + assert ( + await async_subtensor.staking.add_stake( + wallet=dave_wallet, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + amount=Balance.from_tao(1000), + allow_partial_stake=True, + ) + ).success + + dave_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") + + block_, tampo_ = await asyncio.gather( + async_subtensor.block, async_subtensor.subnets.tempo(netuid=bob_subnet_netuid) + ) + # let chain to process the transaction + await async_subtensor.wait_for_block(block_ + tampo_) + + response = await async_subtensor.staking.move_stake( + wallet=dave_wallet, + origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, + origin_netuid=bob_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + move_all_stake=True, + ) + assert response.success is True + + dave_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") + + assert dave_stake.rao == CloseInValue(0, 0.00001) + + +def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): + """ + Tests: + - Adding stake + - Transferring stake from one coldkey-subnet pair to another + """ + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + + assert subtensor.subnets.register_subnet(alice_wallet).success + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + + assert subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ).success + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1_000), + ).success + + alice_stakes = subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + assert alice_stakes == [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + alice_stakes[0].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ), + ] + + bob_stakes = subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + + assert bob_stakes == [] + + dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 + assert subtensor.subnets.register_subnet(dave_wallet).success + + assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) + + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ).success + + response = subtensor.staking.transfer_stake( + alice_wallet, + destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_netuid=dave_subnet_netuid, + amount=alice_stakes[0].stake, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert response.success is True + + alice_stakes = subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + expected_alice_stake = ( + [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance( + alice_stakes[0].stake.rao, alice_subnet_netuid + ), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + alice_stakes[0].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ), + ] + if subtensor.chain.is_fast_blocks() + else [] + ) + + assert alice_stakes == expected_alice_stake + + bob_stakes = subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + + expected_bob_stake = [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=bob_wallet.coldkey.ss58_address, + netuid=dave_subnet_netuid, + stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), + locked=Balance(0).set_unit(dave_subnet_netuid), + emission=get_dynamic_balance( + bob_stakes[0].emission.rao, dave_subnet_netuid + ), + drain=0, + is_registered=False, + ), + ] + assert bob_stakes == expected_bob_stake + + +@pytest.mark.asyncio +async def test_transfer_stake_async( + async_subtensor, alice_wallet, bob_wallet, dave_wallet +): + """ + Tests: + - Adding stake + - Transferring stake from one coldkey-subnet pair to another + """ + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ) + ).success + + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1_000), + ) + ).success + + alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + assert alice_stakes == [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + alice_stakes[0].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ), + ] + + bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + + assert bob_stakes == [] + + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 + assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success + + assert await async_wait_to_start_call( + async_subtensor, dave_wallet, dave_subnet_netuid + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ) + ).success + + response = await async_subtensor.staking.transfer_stake( + alice_wallet, + destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_netuid=dave_subnet_netuid, + amount=alice_stakes[0].stake, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert response.success is True + + alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + expected_alice_stake = ( + [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance( + alice_stakes[0].stake.rao, alice_subnet_netuid + ), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + alice_stakes[0].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ), + ] + if await async_subtensor.chain.is_fast_blocks() + else [] + ) + + assert alice_stakes == expected_alice_stake + + bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + + expected_bob_stake = [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=bob_wallet.coldkey.ss58_address, + netuid=dave_subnet_netuid, + stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), + locked=Balance(0).set_unit(dave_subnet_netuid), + emission=get_dynamic_balance( + bob_stakes[0].emission.rao, dave_subnet_netuid + ), + drain=0, + is_registered=False, + ), + ] + assert bob_stakes == expected_bob_stake + + +# For test we set rate_tolerance=0.7 (70%) because of price is highly dynamic for fast-blocks and 2 SN to avoid ` +# Slippage is too high for the transaction`. This logic controls by the chain. +# Also this test implementation works with non-fast-blocks run. +@pytest.mark.parametrize( + "rate_tolerance", + [None, 1.0], + ids=[ + "Without price limit", + "With price limit", + ], +) +def test_unstaking_with_limit( + subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance +): + """Test unstaking with limits goes well for all subnets with and without price limit.""" + # Register first SN + alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet).success + assert subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( + "Subnet wasn't created successfully" + ) + + wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_2) + + # Register Bob and Dave in SN2 + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_2, + ).success + + assert subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_2, + ).success + + # Register second SN + alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 + assert subtensor.subnets.register_subnet(alice_wallet).success + assert subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( + "Subnet wasn't created successfully" + ) + + wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_3) + + # Register Bob and Dave in SN3 + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + ).success + + assert subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_3, + ).success + + # Check Bob's stakes are empty. + assert ( + subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) + == [] + ) + + # Bob stakes to Dave in both SNs + + assert subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid_2, + amount=Balance.from_tao(10000), + period=16, + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + + assert subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid_3, + amount=Balance.from_tao(15000), + period=16, + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + + # Check that both stakes are presented in result + bob_stakes = subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + assert len(bob_stakes) == 2 + + if rate_tolerance == 0.0001: + # Raise the error + with pytest.raises( + ChainError, match="Slippage is too high for the transaction" + ): + subtensor.staking.unstake_all( + wallet=bob_wallet, + netuid=bob_stakes[0].netuid, + hotkey_ss58=bob_stakes[0].hotkey_ss58, + rate_tolerance=rate_tolerance, + ) + else: + # Successful cases + for si in bob_stakes: + assert subtensor.staking.unstake_all( + wallet=bob_wallet, + netuid=si.netuid, + hotkey_ss58=si.hotkey_ss58, + rate_tolerance=rate_tolerance, + ).success + + # Make sure both unstake were successful. + bob_stakes = subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + assert len(bob_stakes) == 0 + + +@pytest.mark.parametrize( + "rate_tolerance", + [None, 1.0], + ids=[ + "Without price limit", + "With price limit", + ], +) +@pytest.mark.asyncio +async def test_unstaking_with_limit_async( + async_subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance +): + """Test unstaking with limits goes well for all subnets with and without price limit.""" + # Register first SN + alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid_2 + ) + + # Register Bob and Dave in SN2 + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_2, + ) + ).success + + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_2, + ) + ).success + + # Register second SN + alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( + "Subnet wasn't created successfully" + ) + + await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid_3) + + # Register Bob and Dave in SN3 + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + ) + ).success + + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_3, + ) + ).success + + # Check Bob's stakes are empty. + assert ( + await async_subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + == [] + ) + + # Bob stakes to Dave in both SNs + + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid_2, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + amount=Balance.from_tao(10000), + period=16, + ) + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(15000), + period=16, + ) + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + + # Check that both stakes are presented in result + bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + assert len(bob_stakes) == 2 + + if rate_tolerance == 0.0001: + # Raise the error + with pytest.raises( + ChainError, match="Slippage is too high for the transaction" + ): + await async_subtensor.staking.unstake_all( + wallet=bob_wallet, + netuid=bob_stakes[0].netuid, + hotkey_ss58=bob_stakes[0].hotkey_ss58, + rate_tolerance=rate_tolerance, + ) + else: + # Successful cases + for si in bob_stakes: + assert ( + await async_subtensor.staking.unstake_all( + wallet=bob_wallet, + hotkey_ss58=si.hotkey_ss58, + netuid=si.netuid, + rate_tolerance=rate_tolerance, + ) + ).success + + # Make sure both unstake were successful. + bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + assert len(bob_stakes) == 0 From 653a253aa41507872ac971627f060fa2146d490e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:54:35 -0700 Subject: [PATCH 21/23] test GPG --- tests/e2e_tests/test_staking.py | 336 ++++++++++++++++---------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index cd739fa461..ffd6a5a768 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,174 +18,174 @@ from tests.helpers.helpers import CloseInValue -def test_single_operation(subtensor, alice_wallet, bob_wallet): - """ - Tests: - - Staking using `add_stake` - - Unstaking using `unstake` - - Checks StakeInfo - """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - # Register root as Alice - the subnet owner and validator - assert subtensor.subnets.register_subnet(alice_wallet).success - - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - assert stake == Balance(0).set_unit(alice_subnet_netuid) - - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ).success - - stake_alice = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - logging.console.info(f"Alice stake: {stake_alice}") - - stake_bob = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - logging.console.info(f"Bob stake: {stake_bob}") - assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) - - stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) - - expected_stakes = [ - StakeInfo( - hotkey_ss58=stakes[0].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - ] - - fast_blocks_stake = ( - [ - StakeInfo( - hotkey_ss58=stakes[1].hotkey_ss58, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), - locked=Balance(0).set_unit(alice_subnet_netuid), - emission=get_dynamic_balance( - stakes[1].emission.rao, alice_subnet_netuid - ), - drain=0, - is_registered=True, - ) - ] - if subtensor.chain.is_fast_blocks() - else [] - ) - - expected_stakes += fast_blocks_stake - - assert stakes == expected_stakes - assert ( - subtensor.staking.get_stake_for_coldkey - == subtensor.staking.get_stake_info_for_coldkey - ) - - stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, - ) - - assert stakes == { - 0: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=0, - stake=Balance(0), - locked=Balance(0), - emission=Balance(0), - drain=0, - is_registered=False, - ), - 1: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=1, - stake=stake.set_unit(1), - locked=Balance.from_tao(0, netuid=1), - emission=Balance.from_tao(0, netuid=1), - drain=0, - is_registered=False, - ), - 2: StakeInfo( - hotkey_ss58=bob_wallet.hotkey.ss58_address, - coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), - locked=Balance.from_tao(0, netuid=alice_subnet_netuid), - emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), - drain=0, - is_registered=True, - ), - } - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - logging.console.info(f"Alice stake before unstake: {stake}") - - # unstale all to check in later - response = subtensor.staking.unstake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=stake, - period=16, - ) - - assert response.success is True - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - # all balances have been unstaked - assert stake == Balance(0).set_unit(alice_subnet_netuid) +# def test_single_operation(subtensor, alice_wallet, bob_wallet): +# """ +# Tests: +# - Staking using `add_stake` +# - Unstaking using `unstake` +# - Checks StakeInfo +# """ +# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 +# +# # Register root as Alice - the subnet owner and validator +# assert subtensor.subnets.register_subnet(alice_wallet).success +# +# # Verify subnet created successfully +# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) +# +# assert subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# assert stake == Balance(0).set_unit(alice_subnet_netuid) +# +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1), +# period=16, +# ).success +# +# stake_alice = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# logging.console.info(f"Alice stake: {stake_alice}") +# +# stake_bob = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# logging.console.info(f"Bob stake: {stake_bob}") +# assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) +# +# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) +# +# expected_stakes = [ +# StakeInfo( +# hotkey_ss58=stakes[0].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# ] +# +# fast_blocks_stake = ( +# [ +# StakeInfo( +# hotkey_ss58=stakes[1].hotkey_ss58, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), +# locked=Balance(0).set_unit(alice_subnet_netuid), +# emission=get_dynamic_balance( +# stakes[1].emission.rao, alice_subnet_netuid +# ), +# drain=0, +# is_registered=True, +# ) +# ] +# if subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# expected_stakes += fast_blocks_stake +# +# assert stakes == expected_stakes +# assert ( +# subtensor.staking.get_stake_for_coldkey +# == subtensor.staking.get_stake_info_for_coldkey +# ) +# +# stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.hotkey.ss58_address, +# ) +# +# assert stakes == { +# 0: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=0, +# stake=Balance(0), +# locked=Balance(0), +# emission=Balance(0), +# drain=0, +# is_registered=False, +# ), +# 1: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=1, +# stake=stake.set_unit(1), +# locked=Balance.from_tao(0, netuid=1), +# emission=Balance.from_tao(0, netuid=1), +# drain=0, +# is_registered=False, +# ), +# 2: StakeInfo( +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# netuid=alice_subnet_netuid, +# stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), +# locked=Balance.from_tao(0, netuid=alice_subnet_netuid), +# emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), +# drain=0, +# is_registered=True, +# ), +# } +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# logging.console.info(f"Alice stake before unstake: {stake}") +# +# # unstale all to check in later +# response = subtensor.staking.unstake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=stake, +# period=16, +# ) +# +# assert response.success is True +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# # all balances have been unstaked +# assert stake == Balance(0).set_unit(alice_subnet_netuid) @pytest.mark.asyncio From 0e61505d70df4e20b73edf57744ed445871e6034 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:56:34 -0700 Subject: [PATCH 22/23] uncomment one more test --- tests/e2e_tests/test_staking.py | 336 ++++++++++++++++---------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index ffd6a5a768..cd739fa461 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,174 +18,174 @@ from tests.helpers.helpers import CloseInValue -# def test_single_operation(subtensor, alice_wallet, bob_wallet): -# """ -# Tests: -# - Staking using `add_stake` -# - Unstaking using `unstake` -# - Checks StakeInfo -# """ -# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 -# -# # Register root as Alice - the subnet owner and validator -# assert subtensor.subnets.register_subnet(alice_wallet).success -# -# # Verify subnet created successfully -# assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) -# -# assert subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# assert stake == Balance(0).set_unit(alice_subnet_netuid) -# -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1), -# period=16, -# ).success -# -# stake_alice = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# logging.console.info(f"Alice stake: {stake_alice}") -# -# stake_bob = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# logging.console.info(f"Bob stake: {stake_bob}") -# assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) -# -# stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) -# -# expected_stakes = [ -# StakeInfo( -# hotkey_ss58=stakes[0].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# ] -# -# fast_blocks_stake = ( -# [ -# StakeInfo( -# hotkey_ss58=stakes[1].hotkey_ss58, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), -# locked=Balance(0).set_unit(alice_subnet_netuid), -# emission=get_dynamic_balance( -# stakes[1].emission.rao, alice_subnet_netuid -# ), -# drain=0, -# is_registered=True, -# ) -# ] -# if subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# expected_stakes += fast_blocks_stake -# -# assert stakes == expected_stakes -# assert ( -# subtensor.staking.get_stake_for_coldkey -# == subtensor.staking.get_stake_info_for_coldkey -# ) -# -# stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.hotkey.ss58_address, -# ) -# -# assert stakes == { -# 0: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=0, -# stake=Balance(0), -# locked=Balance(0), -# emission=Balance(0), -# drain=0, -# is_registered=False, -# ), -# 1: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=1, -# stake=stake.set_unit(1), -# locked=Balance.from_tao(0, netuid=1), -# emission=Balance.from_tao(0, netuid=1), -# drain=0, -# is_registered=False, -# ), -# 2: StakeInfo( -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# netuid=alice_subnet_netuid, -# stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), -# locked=Balance.from_tao(0, netuid=alice_subnet_netuid), -# emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), -# drain=0, -# is_registered=True, -# ), -# } -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# logging.console.info(f"Alice stake before unstake: {stake}") -# -# # unstale all to check in later -# response = subtensor.staking.unstake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=stake, -# period=16, -# ) -# -# assert response.success is True -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# # all balances have been unstaked -# assert stake == Balance(0).set_unit(alice_subnet_netuid) +def test_single_operation(subtensor, alice_wallet, bob_wallet): + """ + Tests: + - Staking using `add_stake` + - Unstaking using `unstake` + - Checks StakeInfo + """ + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + + # Register root as Alice - the subnet owner and validator + assert subtensor.subnets.register_subnet(alice_wallet).success + + # Verify subnet created successfully + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + + assert subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ).success + + stake_alice = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake: {stake_alice}") + + stake_bob = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + logging.console.info(f"Bob stake: {stake_bob}") + assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) + + stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + + expected_stakes = [ + StakeInfo( + hotkey_ss58=stakes[0].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + + fast_blocks_stake = ( + [ + StakeInfo( + hotkey_ss58=stakes[1].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + stakes[1].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ) + ] + if subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_blocks_stake + + assert stakes == expected_stakes + assert ( + subtensor.staking.get_stake_for_coldkey + == subtensor.staking.get_stake_info_for_coldkey + ) + + stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + ) + + assert stakes == { + 0: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=0, + stake=Balance(0), + locked=Balance(0), + emission=Balance(0), + drain=0, + is_registered=False, + ), + 1: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=1, + stake=stake.set_unit(1), + locked=Balance.from_tao(0, netuid=1), + emission=Balance.from_tao(0, netuid=1), + drain=0, + is_registered=False, + ), + 2: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), + locked=Balance.from_tao(0, netuid=alice_subnet_netuid), + emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + } + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake before unstake: {stake}") + + # unstale all to check in later + response = subtensor.staking.unstake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake, + period=16, + ) + + assert response.success is True + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + # all balances have been unstaked + assert stake == Balance(0).set_unit(alice_subnet_netuid) @pytest.mark.asyncio From fd2fc4f236466643848d899769dd7ee940cadf6a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 12:47:26 -0700 Subject: [PATCH 23/23] e2e tests conftest Finished --- tests/e2e_tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 579d7dcd6c..9fa8dfe8d4 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -326,4 +326,4 @@ def log_test_start_and_end(request): test_name = request.node.nodeid logging.console.info(f"🏁[green]Testing[/green] [yellow]{test_name}[/yellow]") yield - logging.console.success(f"✅ [green]Passed[/green] [yellow]{test_name}[/yellow]") + logging.console.success(f"✅ [green]Finished[/green] [yellow]{test_name}[/yellow]")