From 9acdcc511acaf8b82ebfd4502adb9345bba11633 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 11 Feb 2025 17:28:46 -0800 Subject: [PATCH 01/33] Adds Tao emissions to subnets list --- bittensor_cli/src/bittensor/chain_data.py | 12 +++++++++++- bittensor_cli/src/commands/stake/list.py | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 3b5a4c89a..42beef3d0 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -197,6 +197,7 @@ class StakeInfo(InfoBase): stake: Balance # Stake for the hotkey-coldkey pair locked: Balance # Stake which is locked. emission: Balance # Emission for the hotkey-coldkey pair + tao_emission: Balance # TAO emission for the hotkey-coldkey pair drain: int is_registered: bool @@ -208,11 +209,20 @@ def _fix_decoded(cls, decoded: Any) -> "StakeInfo": stake = Balance.from_rao(decoded.get("stake")).set_unit(netuid) locked = Balance.from_rao(decoded.get("locked")).set_unit(netuid) emission = Balance.from_rao(decoded.get("emission")).set_unit(netuid) + tao_emission = Balance.from_rao(decoded.get("tao_emission")) drain = int(decoded.get("drain")) is_registered = bool(decoded.get("is_registered")) return StakeInfo( - hotkey, coldkey, netuid, stake, locked, emission, drain, is_registered + hotkey, + coldkey, + netuid, + stake, + locked, + emission, + tao_emission, + drain, + is_registered, ) diff --git a/bittensor_cli/src/commands/stake/list.py b/bittensor_cli/src/commands/stake/list.py index 9f2ba7763..21fc46fcb 100644 --- a/bittensor_cli/src/commands/stake/list.py +++ b/bittensor_cli/src/commands/stake/list.py @@ -130,6 +130,11 @@ def define_table( style=COLOR_PALETTE["POOLS"]["EMISSION"], justify="right", ) + table.add_column( + f"[white]Emission \n({Balance.get_unit(0)}/block)", + style=COLOR_PALETTE["POOLS"]["EMISSION"], + justify="right", + ) return table def create_table(hotkey_: str, substakes: list[StakeInfo]): @@ -200,6 +205,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): # Per block emission cell per_block_emission = substake_.emission.tao / pool.tempo + per_block_tao_emission = substake_.tao_emission.tao / pool.tempo # Alpha ownership and TAO ownership cells if alpha_value.tao > 0.00009: if issuance.tao != 0: @@ -243,6 +249,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): # Removing this flag for now, TODO: Confirm correct values are here w.r.t CHKs # if substake_.is_registered # else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block) + str(Balance.from_tao(per_block_tao_emission)), ] ) table = define_table( @@ -343,6 +350,7 @@ def format_cell( "swapped_value": swapped_tao_value.tao, "emission": substake.emission.tao / pool.tempo, "tao_ownership": tao_ownership.tao, + "tao_emission": substake.tao_emission.tao / pool.tempo, } # Get previous values for delta tracking @@ -408,6 +416,16 @@ def format_cell( unit_first=unit_first, precision=4, ) + + tao_emission_value = substake.tao_emission.tao / pool.tempo + tao_emission_cell = format_cell( + tao_emission_value, + prev.get("tao_emission"), + unit="τ", + unit_first=unit_first, + precision=4, + ) + subnet_name_cell = ( f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]" f" {get_subnet_name(dynamic_info[netuid])}" @@ -425,6 +443,7 @@ def format_cell( if substake.is_registered else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status emission_cell, # Emission rate + tao_emission_cell, # TAO emission rate ] ) From c9aed037a929cfa4325cbf7fad14cc6bbca9823c Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 14 Feb 2025 12:23:23 +0200 Subject: [PATCH 02/33] Edge case alpha formatting. --- bittensor_cli/src/bittensor/balances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/balances.py b/bittensor_cli/src/bittensor/balances.py index 3a9c2fc15..b83001634 100644 --- a/bittensor_cli/src/bittensor/balances.py +++ b/bittensor_cli/src/bittensor/balances.py @@ -76,7 +76,7 @@ def __str__(self): if self.unit == UNITS[0]: return f"{self.unit} {float(self.tao):,.4f}" else: - return f"{float(self.tao):,.4f} {self.unit}\u200e" + return f"\u200e{float(self.tao):,.4f} {self.unit}\u200e" def __rich__(self): return "[green]{}[/green][green]{}[/green][green].[/green][dim green]{}[/dim green]".format( From 3402c1535268cf860c5fb91dd5d4e403da4cf45a Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 14 Feb 2025 19:00:04 +0200 Subject: [PATCH 03/33] Allows setting hyperparams arbitrarily, as long as they exist in the AdminUtils pallet. --- bittensor_cli/src/commands/stake/add.py | 8 +- bittensor_cli/src/commands/stake/remove.py | 4 +- bittensor_cli/src/commands/subnets/subnets.py | 8 +- bittensor_cli/src/commands/sudo.py | 120 +++++++++++++----- 4 files changed, 94 insertions(+), 46 deletions(-) diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index cbc1ae242..4aba39e69 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -107,9 +107,7 @@ async def safe_stake_extrinsic( ) return else: - err_out( - f"\n{failure_prelude} with error: {format_error_message(e)}" - ) + err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return else: await response.process_events() @@ -180,9 +178,7 @@ async def stake_extrinsic( extrinsic, wait_for_inclusion=True, wait_for_finalization=False ) except SubstrateRequestException as e: - err_out( - f"\n{failure_prelude} with error: {format_error_message(e)}" - ) + err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return else: await response.process_events() diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index a8d364b5f..8d4629780 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -666,9 +666,7 @@ async def _safe_unstake_extrinsic( ) return else: - err_out( - f"\n{failure_prelude} with error: {format_error_message(e)}" - ) + err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return await response.process_events() diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index e5d397e0f..f4e01486c 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -889,8 +889,8 @@ async def show_root(): total_emission_per_block = 0 for netuid_ in range(len(all_subnets)): subnet = all_subnets[netuid_] - emission_on_subnet = ( - root_state.emission_history[netuid_][idx] / subnet.tempo + emission_on_subnet = root_state.emission_history[netuid_][idx] / ( + subnet.tempo or 1 ) total_emission_per_block += subnet.alpha_to_tao( Balance.from_rao(emission_on_subnet) @@ -2135,9 +2135,7 @@ async def get_identity(subtensor: "SubtensorInterface", netuid: int, title: str title = "Subnet Identity" if not await subtensor.subnet_exists(netuid): - print_error( - f"Subnet {netuid} does not exist." - ) + print_error(f"Subnet {netuid} does not exist.") raise typer.Exit() with console.status( diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 8530f3f94..64395e673 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -18,6 +18,8 @@ normalize_hyperparameters, unlock_key, blocks_to_duration, + float_to_u64, + float_to_u16, ) if TYPE_CHECKING: @@ -70,6 +72,44 @@ def allowed_value( return True, value +def search_metadata( + param_name: str, netuid: int, metadata +) -> tuple[bool, Optional[dict]]: + """ + Searches the substrate metadata AdminUtils pallet for a given parameter name. Crafts a response dict to be used + as call parameters for setting this hyperparameter. + + Args: + param_name: the name of the hyperparameter + netuid: the specified netuid + metadata: the subtensor.substrate.metadata + + Returns: + (success, dict of call params) + + """ + arg_types = {"bool": bool, "u16": float_to_u16, "u64": float_to_u64} + + call_crafter = {"netuid": netuid} + + for pallet in metadata.pallets: + if pallet.name == "AdminUtils": + for call in pallet.calls: + if call.name == param_name: + if "netuid" not in [x.name for x in call.args]: + return False, None + for arg in call.args: + if arg.name == "netuid": + continue + raw_val = input( + f"Enter a value for field '{arg.name}' with type '{arg.typeName}'" + ) + call_crafter[arg.name] = arg_types[arg.typeName](raw_val) + return True, call_crafter + else: + return False, None + + async def set_hyperparameter_extrinsic( subtensor: "SubtensorInterface", wallet: "Wallet", @@ -110,44 +150,53 @@ async def set_hyperparameter_extrinsic( if not unlock_key(wallet).success: return False + arbitrary_extrinsic = False + extrinsic, sudo_ = HYPERPARAMS.get(parameter, ("", False)) if extrinsic is None: - err_console.print(":cross_mark: [red]Invalid hyperparameter specified.[/red]") - return False + arbitrary_extrinsic, call_params = search_metadata( + parameter, netuid, subtensor.substrate.metadata + ) + if not arbitrary_extrinsic: + err_console.print( + ":cross_mark: [red]Invalid hyperparameter specified.[/red]" + ) + return False with console.status( f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{value}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...", spinner="earth", ): - substrate = subtensor.substrate - extrinsic_params = await substrate.get_metadata_call_function( - "AdminUtils", extrinsic - ) - call_params: dict[str, Union[str, bool, float]] = {"netuid": netuid} - - # if input value is a list, iterate through the list and assign values - if isinstance(value, list): - # Ensure that there are enough values for all non-netuid parameters - non_netuid_fields = [ - param["name"] - for param in extrinsic_params["fields"] - if "netuid" not in param["name"] - ] - - if len(value) < len(non_netuid_fields): - raise ValueError( - "Not enough values provided in the list for all parameters" - ) - - call_params.update( - {str(name): val for name, val in zip(non_netuid_fields, value)} + if not arbitrary_extrinsic: + substrate = subtensor.substrate + extrinsic_params = await substrate.get_metadata_call_function( + "AdminUtils", extrinsic ) + call_params = {"netuid": netuid} + + # if input value is a list, iterate through the list and assign values + if isinstance(value, list): + # Ensure that there are enough values for all non-netuid parameters + non_netuid_fields = [ + param["name"] + for param in extrinsic_params["fields"] + if "netuid" not in param["name"] + ] + + if len(value) < len(non_netuid_fields): + raise ValueError( + "Not enough values provided in the list for all parameters" + ) - else: - value_argument = extrinsic_params["fields"][ - len(extrinsic_params["fields"]) - 1 - ] - call_params[str(value_argument["name"])] = value + call_params.update( + {str(name): val for name, val in zip(non_netuid_fields, value)} + ) + + else: + value_argument = extrinsic_params["fields"][ + len(extrinsic_params["fields"]) - 1 + ] + call_params[str(value_argument["name"])] = value # create extrinsic call call_ = await substrate.compose_call( @@ -167,11 +216,16 @@ async def set_hyperparameter_extrinsic( if not success: err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") await asyncio.sleep(0.5) - + elif arbitrary_extrinsic: + console.print( + f":white_heavy_check_mark: " + f"[dark_sea_green3]Hyperparameter {parameter} values changed to {call_params}[/dark_sea_green3]" + ) # Successful registration, final check for membership else: console.print( - f":white_heavy_check_mark: [dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]" + f":white_heavy_check_mark: " + f"[dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]" ) return True @@ -481,7 +535,9 @@ async def sudo_set_hyperparameter( "commit_reveal_weights_enabled", "liquid_alpha_enabled", ]: - normalized_value = param_value.lower() in ["true", "True", "1"] + normalized_value = param_value.lower() in ["true", "1"] + elif param_value in ("True", "False"): + normalized_value = bool(param_value) else: normalized_value = param_value From 4b9c78bc6537d84b456b703634428fbb4e88d7b4 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 14 Feb 2025 19:15:37 +0200 Subject: [PATCH 04/33] We switched to `subtensor.query` which already decodes the value. --- bittensor_cli/src/commands/stake/children_hotkeys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index c0a4914e4..ef50a5232 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -256,7 +256,7 @@ async def get_childkey_take(subtensor, hotkey: str, netuid: int) -> Optional[int params=[hotkey, netuid], ) if childkey_take_: - return int(childkey_take_.value) + return int(childkey_take_) except SubstrateRequestException as e: err_console.print(f"Error querying ChildKeys: {format_error_message(e)}") From 0857ef8acd26189c8971ec2e31b8e248601f9df1 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Fri, 14 Feb 2025 17:28:38 -0800 Subject: [PATCH 05/33] Updates balance command --- .../src/bittensor/subtensor_interface.py | 24 ++++---- bittensor_cli/src/commands/wallets.py | 58 +++++++++++++++---- 2 files changed, 61 insertions(+), 21 deletions(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index cf5ac9e93..fe71bddbd 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -370,7 +370,8 @@ async def get_total_stake_for_coldkey( results = {} for ss58, stake_info_list in sub_stakes.items(): - all_staked_tao = 0 + total_tao_value = Balance(0) + total_swapped_tao_value = Balance(0) for sub_stake in stake_info_list: if sub_stake.stake.rao == 0: continue @@ -381,19 +382,20 @@ async def get_total_stake_for_coldkey( netuid ) - tao_locked = pool.tao_in + # Without slippage + tao_value = pool.alpha_to_tao(alpha_value) + total_tao_value += tao_value - issuance = pool.alpha_out if pool.is_dynamic else tao_locked - tao_ownership = Balance(0) - - if alpha_value.tao > 0.00009 and issuance.tao != 0: - tao_ownership = Balance.from_tao( - (alpha_value.tao / issuance.tao) * tao_locked.tao + # With slippage + if netuid == 0: + swapped_tao_value = tao_value + else: + swapped_tao_value, _, _ = pool.alpha_to_tao_with_slippage( + sub_stake.stake ) + total_swapped_tao_value += swapped_tao_value - all_staked_tao += tao_ownership.rao - - results[ss58] = Balance.from_rao(all_staked_tao) + results[ss58] = (total_tao_value, total_swapped_tao_value) return results async def get_total_stake_for_hotkey( diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index e3a3e8d64..237f401f1 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -284,11 +284,7 @@ async def wallet_balance( """Retrieves the current balance of the specified wallet""" if ss58_addresses: coldkeys = ss58_addresses - identities = await subtensor.query_all_identities() - wallet_names = [ - f"{identities.get(coldkey, {'name': f'Provided address {i}'})['name']}" - for i, coldkey in enumerate(coldkeys) - ] + wallet_names = [f"Provided Address {i + 1}" for i in range(len(ss58_addresses))] elif not all_balances: if not wallet.coldkeypub_file.exists_on_device(): @@ -307,19 +303,29 @@ async def wallet_balance( wallet_names = [wallet.name] block_hash = await subtensor.substrate.get_chain_head() - free_balances = await subtensor.get_balances(*coldkeys, block_hash=block_hash) + free_balances, staked_balances = await asyncio.gather( + subtensor.get_balances(*coldkeys, block_hash=block_hash), + subtensor.get_total_stake_for_coldkey(*coldkeys, block_hash=block_hash), + ) total_free_balance = sum(free_balances.values()) + total_staked_balance = sum(stake[0] for stake in staked_balances.values()) + total_staked_with_slippage = sum(stake[1] for stake in staked_balances.values()) balances = { - name: (coldkey, free_balances[coldkey]) + name: ( + coldkey, + free_balances[coldkey], + staked_balances[coldkey][0], + staked_balances[coldkey][1], + ) for (name, coldkey) in zip(wallet_names, coldkeys) } table = Table( Column( "[white]Wallet Name", - style="bold bright_cyan", + style=COLOR_PALETTE["GENERAL"]["SUBHEADING_MAIN"], no_wrap=True, ), Column( @@ -333,7 +339,31 @@ async def wallet_balance( style=COLOR_PALETTE["GENERAL"]["BALANCE"], no_wrap=True, ), - title=f"\n [{COLOR_PALETTE['GENERAL']['HEADER']}]Wallet Coldkey Balance\nNetwork: {subtensor.network}", + Column( + "[white]Staked Value", + justify="right", + style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"], + no_wrap=True, + ), + Column( + "[white]Staked (w/slippage)", + justify="right", + style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"], + no_wrap=True, + ), + Column( + "[white]Total Balance", + justify="right", + style=COLOR_PALETTE["GENERAL"]["BALANCE"], + no_wrap=True, + ), + Column( + "[white]Total (w/slippage)", + justify="right", + style=COLOR_PALETTE["GENERAL"]["BALANCE"], + no_wrap=True, + ), + title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Wallet Coldkey Balance[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Network: {subtensor.network}\n", show_footer=True, show_edge=False, border_style="bright_black", @@ -343,17 +373,25 @@ async def wallet_balance( leading=True, ) - for name, (coldkey, free) in balances.items(): + for name, (coldkey, free, staked, staked_slippage) in balances.items(): table.add_row( name, coldkey, str(free), + str(staked), + str(staked_slippage), + str(free + staked), + str(free + staked_slippage), ) table.add_row() table.add_row( "Total Balance", "", str(total_free_balance), + str(total_staked_balance), + str(total_staked_with_slippage), + str(total_free_balance + total_staked_balance), + str(total_free_balance + total_staked_with_slippage), ) console.print(Padding(table, (0, 0, 0, 4))) await subtensor.substrate.close() From 5325871e4f106a4a5c1629666cb6b59d9730a4de Mon Sep 17 00:00:00 2001 From: Igor Date: Sun, 16 Feb 2025 15:05:07 -0600 Subject: [PATCH 06/33] remove version --- .github/workflows/e2e-subtensor-tests.yml | 4 ++-- bittensor_cli/__init__.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yml b/.github/workflows/e2e-subtensor-tests.yml index 1c6251802..e0c801590 100644 --- a/.github/workflows/e2e-subtensor-tests.yml +++ b/.github/workflows/e2e-subtensor-tests.yml @@ -53,8 +53,8 @@ jobs: - name: Add wasm32-unknown-unknown target run: | - rustup target add wasm32-unknown-unknown --toolchain stable-x86_64-unknown-linux-gnu - rustup component add rust-src --toolchain stable-x86_64-unknown-linux-gnu + rustup target add wasm32-unknown-unknown + rustup component add rust-src - name: Clone subtensor repo run: git clone https://github.com/opentensor/subtensor.git diff --git a/bittensor_cli/__init__.py b/bittensor_cli/__init__.py index 55ce167e0..f0ed64472 100644 --- a/bittensor_cli/__init__.py +++ b/bittensor_cli/__init__.py @@ -15,9 +15,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from .cli import CLIManager +from .cli import CLIManager, __version__ -__version__ = "9.0.0" - __all__ = ["CLIManager", "__version__"] From a6abc8e051adc4d5ede4090b990ada5fa74e0fd4 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 18 Feb 2025 18:23:40 +0200 Subject: [PATCH 07/33] More efficient method. --- bittensor_cli/src/commands/sudo.py | 33 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 64395e673..ca1eb7e5c 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -73,7 +73,7 @@ def allowed_value( def search_metadata( - param_name: str, netuid: int, metadata + param_name: str, value: Union[str, bool, float, list[float]], netuid: int, metadata ) -> tuple[bool, Optional[dict]]: """ Searches the substrate metadata AdminUtils pallet for a given parameter name. Crafts a response dict to be used @@ -81,6 +81,7 @@ def search_metadata( Args: param_name: the name of the hyperparameter + value: the value to set the hyperparameter netuid: the specified netuid metadata: the subtensor.substrate.metadata @@ -88,7 +89,19 @@ def search_metadata( (success, dict of call params) """ + + def type_converter_with_retry(type_, val, arg_name): + try: + if val is None: + val = input( + f"Enter a value for field '{arg_name}' with type '{arg_type_output[type_]}'" + ) + return arg_types[type_](val) + except ValueError: + return type_converter_with_retry(type_, None, arg_name) + arg_types = {"bool": bool, "u16": float_to_u16, "u64": float_to_u64} + arg_type_output = {"bool": bool, "u16": float, "u64": float} call_crafter = {"netuid": netuid} @@ -98,13 +111,17 @@ def search_metadata( if call.name == param_name: if "netuid" not in [x.name for x in call.args]: return False, None - for arg in call.args: - if arg.name == "netuid": - continue - raw_val = input( - f"Enter a value for field '{arg.name}' with type '{arg.typeName}'" + call_args = [arg for arg in call.args if arg.name != "netuid"] + if len(call_args) == 1: + arg = call_args[0] + call_crafter[arg.name] = type_converter_with_retry( + arg.typeName, value, arg.name ) - call_crafter[arg.name] = arg_types[arg.typeName](raw_val) + else: + for arg in call_args: + call_crafter[arg.name] = type_converter_with_retry( + arg.typeName, None, arg.name + ) return True, call_crafter else: return False, None @@ -155,7 +172,7 @@ async def set_hyperparameter_extrinsic( extrinsic, sudo_ = HYPERPARAMS.get(parameter, ("", False)) if extrinsic is None: arbitrary_extrinsic, call_params = search_metadata( - parameter, netuid, subtensor.substrate.metadata + parameter, value, netuid, subtensor.substrate.metadata ) if not arbitrary_extrinsic: err_console.print( From c27246fba3cc9d102a4be525567219f5875e620b Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 08:23:48 -0800 Subject: [PATCH 08/33] merges staging --- bittensor_cli/src/commands/stake/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/list.py b/bittensor_cli/src/commands/stake/list.py index 64914c2ed..ab14ba433 100644 --- a/bittensor_cli/src/commands/stake/list.py +++ b/bittensor_cli/src/commands/stake/list.py @@ -206,7 +206,7 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): # Per block emission cell per_block_emission = substake_.emission.tao / (pool.tempo or 1) - per_block_tao_emission = unstake.tao_emission.tao / (pool.tempo or 1) + per_block_tao_emission = substake_.tao_emission.tao / (pool.tempo or 1) # Alpha ownership and TAO ownership cells if alpha_value.tao > 0.00009: if issuance.tao != 0: From de235ac630b1324b9c471443fbc97c4b62048093 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 08:27:14 -0800 Subject: [PATCH 09/33] Handles div by zero --- bittensor_cli/src/commands/stake/list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/stake/list.py b/bittensor_cli/src/commands/stake/list.py index ab14ba433..2545725ec 100644 --- a/bittensor_cli/src/commands/stake/list.py +++ b/bittensor_cli/src/commands/stake/list.py @@ -351,7 +351,7 @@ def format_cell( "swapped_value": swapped_tao_value.tao, "emission": substake.emission.tao / (pool.tempo or 1), "tao_ownership": tao_ownership.tao, - "tao_emission": substake.tao_emission.tao / pool.tempo, + "tao_emission": substake.tao_emission.tao (pool.tempo or 1), } # Get previous values for delta tracking @@ -418,7 +418,7 @@ def format_cell( precision=4, ) - tao_emission_value = substake.tao_emission.tao / pool.tempo + tao_emission_value = substake.tao_emission.tao / (pool.tempo or 1) tao_emission_cell = format_cell( tao_emission_value, prev.get("tao_emission"), From 44e124fe492558adc14b6001b49ad2b30aec115e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 08:34:45 -0800 Subject: [PATCH 10/33] Updates chain_data -> cls() --- bittensor_cli/src/bittensor/chain_data.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index f534d2246..9d71c675e 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -156,7 +156,7 @@ class SubnetHyperparameters(InfoBase): def _fix_decoded( cls, decoded: Union[dict, "SubnetHyperparameters"] ) -> "SubnetHyperparameters": - return SubnetHyperparameters( + return cls( rho=decoded.get("rho"), kappa=decoded.get("kappa"), immunity_period=decoded.get("immunity_period"), @@ -213,7 +213,7 @@ def _fix_decoded(cls, decoded: Any) -> "StakeInfo": drain = int(decoded.get("drain")) is_registered = bool(decoded.get("is_registered")) - return StakeInfo( + return cls( hotkey, coldkey, netuid, @@ -303,7 +303,7 @@ def _fix_decoded(cls, decoded: Any) -> "NeuronInfo": axon_info = decoded.get("axon_info", {}) coldkey = decode_account_id(decoded.get("coldkey")) hotkey = decode_account_id(decoded.get("hotkey")) - return NeuronInfo( + return cls( hotkey=hotkey, coldkey=coldkey, uid=decoded.get("uid"), @@ -565,7 +565,7 @@ class SubnetInfo(InfoBase): @classmethod def _fix_decoded(cls, decoded: "SubnetInfo") -> "SubnetInfo": - return SubnetInfo( + return cls( netuid=decoded.get("netuid"), rho=decoded.get("rho"), kappa=decoded.get("kappa"), @@ -604,7 +604,7 @@ class SubnetIdentity(InfoBase): @classmethod def _fix_decoded(cls, decoded: dict) -> "SubnetIdentity": - return SubnetIdentity( + return cls( subnet_name=bytes(decoded["subnet_name"]).decode(), github_repo=bytes(decoded["github_repo"]).decode(), subnet_contact=bytes(decoded["subnet_contact"]).decode(), @@ -838,7 +838,7 @@ class SubnetState(InfoBase): @classmethod def _fix_decoded(cls, decoded: Any) -> "SubnetState": netuid = decoded.get("netuid") - return SubnetState( + return cls( netuid=netuid, hotkeys=[decode_account_id(val) for val in decoded.get("hotkeys")], coldkeys=[decode_account_id(val) for val in decoded.get("coldkeys")], From 747efa1b1a38dcfe675d9442985d45cd0a0dff02 Mon Sep 17 00:00:00 2001 From: Igor Date: Tue, 18 Feb 2025 11:24:08 -0600 Subject: [PATCH 11/33] fix feedback --- .github/workflows/e2e-subtensor-tests.yml | 4 ++-- bittensor_cli/__init__.py | 23 +++++++++++++++++++++-- bittensor_cli/cli.py | 17 ++++------------- setup.py | 2 +- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yml b/.github/workflows/e2e-subtensor-tests.yml index e0c801590..1c6251802 100644 --- a/.github/workflows/e2e-subtensor-tests.yml +++ b/.github/workflows/e2e-subtensor-tests.yml @@ -53,8 +53,8 @@ jobs: - name: Add wasm32-unknown-unknown target run: | - rustup target add wasm32-unknown-unknown - rustup component add rust-src + rustup target add wasm32-unknown-unknown --toolchain stable-x86_64-unknown-linux-gnu + rustup component add rust-src --toolchain stable-x86_64-unknown-linux-gnu - name: Clone subtensor repo run: git clone https://github.com/opentensor/subtensor.git diff --git a/bittensor_cli/__init__.py b/bittensor_cli/__init__.py index f0ed64472..5d9f88240 100644 --- a/bittensor_cli/__init__.py +++ b/bittensor_cli/__init__.py @@ -15,7 +15,26 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from .cli import CLIManager, __version__ +from .cli import CLIManager +def version_as_int(version): + import re + _core_version = re.match(r"^\d+\.\d+\.\d+", version).group(0) + _version_split = _core_version.split(".") + __version_info__ = tuple(int(part) for part in _version_split) + _version_int_base = 1000 + assert max(__version_info__) < _version_int_base -__all__ = ["CLIManager", "__version__"] + __version_as_int__: int = sum( + e * (_version_int_base**i) for i, e in enumerate(reversed(__version_info__)) + ) + assert __version_as_int__ < 2**31 # fits in int32 + __new_signature_version__ = 360 + return __version_as_int__ + +__version__ = "9.0.0" +__version_as_int__ = version_as_int(__version__) + + + +__all__ = ["CLIManager", "__version__", "__version_as_int__"] diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0de749e2c..0c7b1241d 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -72,21 +72,8 @@ class GitError(Exception): pass -__version__ = "9.0.0" -_core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0) -_version_split = _core_version.split(".") -__version_info__ = tuple(int(part) for part in _version_split) -_version_int_base = 1000 -assert max(__version_info__) < _version_int_base - -__version_as_int__: int = sum( - e * (_version_int_base**i) for i, e in enumerate(reversed(__version_info__)) -) -assert __version_as_int__ < 2**31 # fits in int32 -__new_signature_version__ = 360 - _epilog = "Made with [bold red]:heart:[/bold red] by The Openτensor Foundaτion" np.set_printoptions(precision=8, suppress=True, floatmode="fixed") @@ -484,6 +471,8 @@ def version_callback(value: bool): """ Prints the current version/branch-name """ + from bittensor_cli import __version__ + if value: try: repo = Repo(os.path.dirname(os.path.dirname(__file__))) @@ -4915,6 +4904,7 @@ def weights_reveal( validate=WV.WALLET_AND_HOTKEY, ) + from bittensor_cli import __version_as_int__ return self._run_command( weights_cmds.reveal_weights( self.initialize_chain(network), @@ -5012,6 +5002,7 @@ def weights_commit( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) + from bittensor_cli import __version_as_int__ return self._run_command( weights_cmds.commit_weights( self.initialize_chain(network), diff --git a/setup.py b/setup.py index f9c92cda8..d827cb4b8 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def read_requirements(path): # loading version from setup.py with codecs.open( - os.path.join(here, "bittensor_cli/cli.py"), encoding="utf-8" + os.path.join(here, "bittensor_cli/__init__.py"), encoding="utf-8" ) as init_file: version_match = re.search( r"^__version__ = ['\"]([^'\"]*)['\"]", init_file.read(), re.M From bfc875683e976e0c27bafcc85528ca02f17aa5d0 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 12:15:19 -0800 Subject: [PATCH 12/33] Updates return type --- bittensor_cli/src/bittensor/subtensor_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index fe71bddbd..ff687fe1b 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -352,7 +352,7 @@ async def get_total_stake_for_coldkey( *ss58_addresses, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, Balance]: + ) -> dict[str, tuple[Balance, Balance]]: """ Returns the total stake held on a coldkey. From 600676f830bf05a5fc728aafd897f8285f5a82c6 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 16:53:55 -0800 Subject: [PATCH 13/33] Adds unstaking from all hotkeys + tests --- bittensor_cli/cli.py | 32 ++- bittensor_cli/src/commands/stake/remove.py | 313 ++++++++++++++------- tests/e2e_tests/test_unstaking.py | 281 ++++++++++++++++++ 3 files changed, 523 insertions(+), 103 deletions(-) create mode 100644 tests/e2e_tests/test_unstaking.py diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0de749e2c..76be6debe 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3221,11 +3221,24 @@ def stake_remove( else: print_error("Invalid hotkey ss58 address.") raise typer.Exit() - else: - hotkey_or_ss58 = Prompt.ask( - "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from", - default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + elif all_hotkeys: + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH], ) + else: + if not hotkey_ss58_address and not wallet_hotkey: + hotkey_or_ss58 = Prompt.ask( + "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from [dim](or enter 'all' to unstake from all hotkeys)[/dim]", + default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + ) + elif hotkey_ss58_address: + hotkey_or_ss58 = hotkey_ss58_address + else: + hotkey_or_ss58 = wallet_hotkey + if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 wallet = self.wallet_ask( @@ -3234,6 +3247,14 @@ def stake_remove( wallet_hotkey, ask_for=[WO.NAME, WO.PATH], ) + elif hotkey_or_ss58 == "all": + all_hotkeys = True + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH], + ) else: wallet_hotkey = hotkey_or_ss58 wallet = self.wallet_ask( @@ -3249,6 +3270,9 @@ def stake_remove( subtensor=self.initialize_chain(network), hotkey_ss58_address=hotkey_ss58_address, unstake_all_alpha=unstake_all_alpha, + all_hotkeys=all_hotkeys, + include_hotkeys=include_hotkeys, + exclude_hotkeys=exclude_hotkeys, prompt=prompt, ) ) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index a8d364b5f..048855fdc 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -49,26 +49,36 @@ async def unstake( f"Retrieving subnet data & identities from {subtensor.network}...", spinner="earth", ): - all_sn_dynamic_info_, ck_hk_identities, old_identities = await asyncio.gather( - subtensor.all_subnets(), - subtensor.fetch_coldkey_hotkey_identities(), - subtensor.get_delegate_identities(), + chain_head = await subtensor.substrate.get_chain_head() + ( + all_sn_dynamic_info_, + ck_hk_identities, + old_identities, + stake_infos, + ) = await asyncio.gather( + subtensor.all_subnets(block_hash=chain_head), + subtensor.fetch_coldkey_hotkey_identities(block_hash=chain_head), + subtensor.get_delegate_identities(block_hash=chain_head), + subtensor.get_stake_for_coldkey( + wallet.coldkeypub.ss58_address, block_hash=chain_head + ), ) all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_} if interactive: hotkeys_to_unstake_from, unstake_all_from_hk = await _unstake_selection( - subtensor, - wallet, all_sn_dynamic_info, ck_hk_identities, old_identities, + stake_infos, netuid=netuid, ) if unstake_all_from_hk: hotkey_to_unstake_all = hotkeys_to_unstake_from[0] unstake_all_alpha = Confirm.ask( - "\nUnstake [blue]all alpha stakes[/blue] and stake back to [blue]root[/blue]? (No will unstake everything)", + "\nDo you want to:\n" + "[blue]Yes[/blue]: Unstake from all subnets and automatically restake to subnet 0 (root)\n" + "[blue]No[/blue]: Unstake everything (including subnet 0)", default=True, ) return await unstake_all( @@ -96,20 +106,17 @@ async def unstake( all_hotkeys=all_hotkeys, include_hotkeys=include_hotkeys, exclude_hotkeys=exclude_hotkeys, + stake_infos=stake_infos, + identities=ck_hk_identities, + old_identities=old_identities, ) with console.status( f"Retrieving stake data from {subtensor.network}...", spinner="earth", ): - # Fetch stake balances - chain_head = await subtensor.substrate.get_chain_head() - stake_info_list = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - block_hash=chain_head, - ) stake_in_netuids = {} - for stake_info in stake_info_list: + for stake_info in stake_infos: if stake_info.hotkey_ss58 not in stake_in_netuids: stake_in_netuids[stake_info.hotkey_ss58] = {} stake_in_netuids[stake_info.hotkey_ss58][stake_info.netuid] = ( @@ -313,6 +320,9 @@ async def unstake_all( subtensor: "SubtensorInterface", hotkey_ss58_address: str, unstake_all_alpha: bool = False, + all_hotkeys: bool = False, + include_hotkeys: list[str] = [], + exclude_hotkeys: list[str] = [], prompt: bool = True, ) -> bool: """Unstakes all stakes from all hotkeys in all subnets.""" @@ -334,10 +344,27 @@ async def unstake_all( subtensor.all_subnets(), subtensor.get_balance(wallet.coldkeypub.ss58_address), ) - if not hotkey_ss58_address: - hotkey_ss58_address = wallet.hotkey.ss58_address + + if all_hotkeys: + hotkeys = _get_hotkeys_to_unstake( + wallet, + hotkey_ss58_address=hotkey_ss58_address, + all_hotkeys=all_hotkeys, + include_hotkeys=include_hotkeys, + exclude_hotkeys=exclude_hotkeys, + stake_infos=stake_info, + identities=ck_hk_identities, + old_identities=old_identities, + ) + elif not hotkey_ss58_address: + hotkeys = [(wallet.hotkey_str, wallet.hotkey.ss58_address)] + else: + hotkeys = [(None, hotkey_ss58_address)] + + hotkey_names = {ss58: name for name, ss58 in hotkeys if name is not None} + hotkey_ss58s = [ss58 for _, ss58 in hotkeys] stake_info = [ - stake for stake in stake_info if stake.hotkey_ss58 == hotkey_ss58_address + stake for stake in stake_info if stake.hotkey_ss58 in hotkey_ss58s ] if unstake_all_alpha: @@ -403,18 +430,7 @@ async def unstake_all( if stake.stake.rao == 0: continue - # Get hotkey identity - if hk_identity := ck_hk_identities["hotkeys"].get(stake.hotkey_ss58): - hotkey_name = hk_identity.get("identity", {}).get( - "name", "" - ) or hk_identity.get("display", "~") - hotkey_display = f"{hotkey_name}" - elif old_identity := old_identities.get(stake.hotkey_ss58): - hotkey_name = old_identity.display - hotkey_display = f"{hotkey_name}" - else: - hotkey_display = stake.hotkey_ss58 - + hotkey_display = hotkey_names.get(stake.hotkey_ss58, stake.hotkey_ss58) subnet_info = all_sn_dynamic_info.get(stake.netuid) stake_amount = stake.stake received_amount, slippage_pct, slippage_pct_float = _calculate_slippage( @@ -455,56 +471,16 @@ async def unstake_all( err_console.print("Error decrypting coldkey (possibly incorrect password)") return False - console_status = ( - ":satellite: Unstaking all Alpha stakes..." - if unstake_all_alpha - else ":satellite: Unstaking all stakes..." - ) - previous_root_stake = await subtensor.get_stake( - hotkey_ss58=hotkey_ss58_address, - coldkey_ss58=wallet.coldkeypub.ss58_address, - netuid=0, - ) - with console.status(console_status): - call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all" - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params={"hotkey": hotkey_ss58_address}, - ) - success, error_message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=True, - wait_for_finalization=False, - ) - - if success: - success_message = ( - ":white_heavy_check_mark: [green]Successfully unstaked all stakes[/green]" - if not unstake_all_alpha - else ":white_heavy_check_mark: [green]Successfully unstaked all Alpha stakes[/green]" - ) - console.print(success_message) - new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - console.print( - f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" - ) - if unstake_all_alpha: - root_stake = await subtensor.get_stake( - hotkey_ss58=hotkey_ss58_address, - coldkey_ss58=wallet.coldkeypub.ss58_address, - netuid=0, - ) - console.print( - f"Root Stake:\n [blue]{previous_root_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{root_stake}" - ) - return True - else: - err_console.print( - f":cross_mark: [red]Failed to unstake[/red]: {error_message}" + with console.status("Unstaking all stakes...") as status: + for hotkey_ss58 in hotkey_ss58s: + await _unstake_all_extrinsic( + wallet=wallet, + subtensor=subtensor, + hotkey_ss58=hotkey_ss58, + hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58), + unstake_all_alpha=unstake_all_alpha, + status=status, ) - return False # Extrinsics @@ -666,9 +642,7 @@ async def _safe_unstake_extrinsic( ) return else: - err_out( - f"\n{failure_prelude} with error: {format_error_message(e)}" - ) + err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return await response.process_events() @@ -709,6 +683,115 @@ async def _safe_unstake_extrinsic( ) +async def _unstake_all_extrinsic( + wallet: Wallet, + subtensor: "SubtensorInterface", + hotkey_ss58: str, + hotkey_name: str, + unstake_all_alpha: bool, + status=None, +) -> None: + """Execute an unstake all extrinsic. + + Args: + wallet: Wallet instance + subtensor: Subtensor interface + hotkey_ss58: Hotkey SS58 address + hotkey_name: Display name of the hotkey + unstake_all_alpha: Whether to unstake only alpha stakes + status: Optional status for console updates + """ + err_out = partial(print_error, status=status) + failure_prelude = ( + f":cross_mark: [red]Failed[/red] to unstake all from {hotkey_name}" + ) + + if status: + status.update( + f"\n:satellite: {'Unstaking all Alpha stakes' if unstake_all_alpha else 'Unstaking all stakes'} from {hotkey_name} ..." + ) + + block_hash = await subtensor.substrate.get_chain_head() + if unstake_all_alpha: + previous_root_stake, current_balance = await asyncio.gather( + subtensor.get_stake( + hotkey_ss58=hotkey_ss58, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=0, + block_hash=block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + ) + else: + current_balance = await subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ) + + call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all" + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params={"hotkey": hotkey_ss58}, + ) + + try: + response = await subtensor.substrate.submit_extrinsic( + extrinsic=await subtensor.substrate.create_signed_extrinsic( + call=call, + keypair=wallet.coldkey, + ), + wait_for_inclusion=True, + wait_for_finalization=False, + ) + await response.process_events() + + if not await response.is_success: + err_out( + f"{failure_prelude} with error: " + f"{format_error_message(await response.error_message)}" + ) + return + + # Fetch latest balance and stake + block_hash = await subtensor.substrate.get_chain_head() + if unstake_all_alpha: + new_root_stake, new_balance = await asyncio.gather( + subtensor.get_stake( + hotkey_ss58=hotkey_ss58, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=0, + block_hash=block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + ) + else: + new_balance = await subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ) + + success_message = ( + ":white_heavy_check_mark: [green]Finalized: Successfully unstaked all stakes[/green]" + if not unstake_all_alpha + else ":white_heavy_check_mark: [green]Finalized: Successfully unstaked all Alpha stakes[/green]" + ) + console.print(f"{success_message} from {hotkey_name}") + console.print( + f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}" + ) + + if unstake_all_alpha: + console.print( + f"Root Stake for {hotkey_name}:\n [blue]{previous_root_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_root_stake}" + ) + + except Exception as e: + err_out(f"{failure_prelude} with error: {str(e)}") + + # Helpers def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, float]: """Calculate slippage and received amount for unstaking operation. @@ -737,17 +820,12 @@ def _calculate_slippage(subnet_info, amount: Balance) -> tuple[Balance, str, flo async def _unstake_selection( - subtensor: "SubtensorInterface", - wallet: Wallet, dynamic_info, identities, old_identities, + stake_infos, netuid: Optional[int] = None, ): - stake_infos = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address - ) - if not stake_infos: print_error("You have no stakes to unstake.") raise typer.Exit() @@ -771,16 +849,11 @@ async def _unstake_selection( hotkeys_info = [] for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()): - if hk_identity := identities["hotkeys"].get(hotkey_ss58): - hotkey_name = hk_identity.get("identity", {}).get( - "name", "" - ) or hk_identity.get("display", "~") - elif old_identity := old_identities.get(hotkey_ss58): - hotkey_name = old_identity.display - else: - hotkey_name = "~" - # TODO: Add wallet ids here. - + hotkey_name = get_hotkey_identity( + hotkey_ss58=hotkey_ss58, + identities=identities, + old_identities=old_identities, + ) hotkeys_info.append( { "index": idx, @@ -983,6 +1056,9 @@ def _get_hotkeys_to_unstake( all_hotkeys: bool, include_hotkeys: list[str], exclude_hotkeys: list[str], + stake_infos: list, + identities: dict, + old_identities: dict, ) -> list[tuple[Optional[str], str]]: """Get list of hotkeys to unstake from based on input parameters. @@ -1002,13 +1078,27 @@ def _get_hotkeys_to_unstake( if all_hotkeys: print_verbose("Unstaking from all hotkeys") - all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet) - return [ + all_hotkeys_ = get_hotkey_wallets_for_wallet(wallet=wallet) + wallet_hotkeys = [ (wallet.hotkey_str, wallet.hotkey.ss58_address) for wallet in all_hotkeys_ if wallet.hotkey_str not in exclude_hotkeys ] + wallet_hotkey_addresses = {addr for _, addr in wallet_hotkeys} + chain_hotkeys = [ + ( + get_hotkey_identity(stake_info.hotkey_ss58, identities, old_identities), + stake_info.hotkey_ss58, + ) + for stake_info in stake_infos + if ( + stake_info.hotkey_ss58 not in wallet_hotkey_addresses + and stake_info.hotkey_ss58 not in exclude_hotkeys + ) + ] + return wallet_hotkeys + chain_hotkeys + if include_hotkeys: print_verbose("Unstaking from included hotkeys") result = [] @@ -1144,3 +1234,28 @@ def _print_table_and_slippage( - [bold white]Partial unstaking[/bold white]: If True, allows unstaking up to the rate tolerance limit. If False, the entire transaction will fail if rate tolerance is exceeded.\n""" console.print(base_description + (safe_staking_description if safe_staking else "")) + + +def get_hotkey_identity( + hotkey_ss58: str, + identities: dict, + old_identities: dict, +) -> str: + """Get identity name for a hotkey from identities or old_identities. + + Args: + hotkey_ss58 (str): The hotkey SS58 address + identities (dict): Current identities from fetch_coldkey_hotkey_identities + old_identities (dict): Old identities from get_delegate_identities + + Returns: + str: Identity name or truncated address + """ + if hk_identity := identities["hotkeys"].get(hotkey_ss58): + return hk_identity.get("identity", {}).get("name", "") or hk_identity.get( + "display", "~" + ) + elif old_identity := old_identities.get(hotkey_ss58): + return old_identity.display + else: + return f"{hotkey_ss58[:4]}...{hotkey_ss58[-4:]}" diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py new file mode 100644 index 000000000..c401654e5 --- /dev/null +++ b/tests/e2e_tests/test_unstaking.py @@ -0,0 +1,281 @@ +import re + +from bittensor_cli.src.bittensor.balances import Balance + + +def test_unstaking(local_chain, wallet_setup): + """ + Test various unstaking scenarios including partial unstake, unstake all alpha, and unstake all. + + Steps: + 1. Create wallets for Alice and Bob + 2. Create 2 subnets with Alice + 3. Register Bob in one subnet + 4. Add stake from Bob to all subnets (except 1) + 5. Remove partial stake from one subnet and verify + 6. Remove all alpha stake and verify + 7. Add stake again to both subnets + 8. Remove all stake and verify + """ + print("Testing unstaking scenarios 🧪") + + # Create wallets for Alice and Bob + wallet_path_alice = "//Alice" + wallet_path_bob = "//Bob" + + # Setup Alice's wallet + keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( + wallet_path_alice + ) + + # Setup Bob's wallet + keypair_bob, wallet_bob, wallet_path_bob, exec_command_bob = wallet_setup( + wallet_path_bob + ) + + # Create first subnet (netuid = 2) + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--chain", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--name", + "Test Subnet 2", + "--repo", + "https://github.com/username/repo", + "--contact", + "test@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "test#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Test subnet", + "--no-prompt", + ], + ) + assert "✅ Registered subnetwork with netuid: 2" in result.stdout + + # Create second subnet (netuid = 3) + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--chain", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--name", + "Test Subnet 3", + "--repo", + "https://github.com/username/repo", + "--contact", + "test@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "test#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Test subnet", + "--no-prompt", + ], + ) + assert "✅ Registered subnetwork with netuid: 3" in result.stdout + + # Register Bob in both subnets + register_result = exec_command_bob( + command="subnets", + sub_command="register", + extra_args=[ + "--netuid", + "2", + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert "✅ Registered" in register_result.stdout + + # Add stake to subnets + for netuid in [0, 2, 3]: + stake_result = exec_command_bob( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--amount", + "700", + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--partial", + "--tolerance", + "0.5", + ], + ) + assert "✅ Finalized" in stake_result.stdout + + stake_list = exec_command_bob( + command="stake", + sub_command="list", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--verbose", + ], + ) + + cleaned_stake = [ + re.sub(r"\s+", " ", line) for line in stake_list.stdout.splitlines() + ] + inital_stake_netuid_2 = cleaned_stake[9].split("│")[3].strip().split()[0] + + # Remove partial stake from netuid 2 + partial_unstake_netuid_2 = exec_command_bob( + command="stake", + sub_command="remove", + extra_args=[ + "--netuid", + "2", + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--amount", + "100", + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--partial", + "--tolerance", + "0.5", + ], + ) + assert "✅ Finalized" in partial_unstake_netuid_2.stdout + + # Verify partial unstake + stake_list = exec_command_bob( + command="stake", + sub_command="list", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--verbose", + ], + ) + + # Verify stake amounts after partial unstake + cleaned_stake = [ + re.sub(r"\s+", " ", line) for line in stake_list.stdout.splitlines() + ] + stake_after_unstaking_netuid_2 = cleaned_stake[9].split("│")[3].strip().split()[0] + assert Balance.from_tao(float(stake_after_unstaking_netuid_2)) <= Balance.from_tao( + float(inital_stake_netuid_2) + ) + + # Remove all alpha stakes + unstake_alpha = exec_command_bob( + command="stake", + sub_command="remove", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--chain", + "ws://127.0.0.1:9945", + "--all-alpha", + "--no-prompt", + ], + ) + assert ( + "✅ Finalized: Successfully unstaked all Alpha stakes" in unstake_alpha.stdout + ) + + # Add stake again to subnets + for netuid in [0, 2, 3]: + stake_result = exec_command_bob( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--amount", + "300", + "--chain", + "ws://127.0.0.1:9945", + "--no-prompt", + "--partial", + "--tolerance", + "0.5", + ], + ) + assert "✅ Finalized" in stake_result.stdout + + # Remove all stakes + unstake_all = exec_command_bob( + command="stake", + sub_command="remove", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--chain", + "ws://127.0.0.1:9945", + "--all", + "--no-prompt", + ], + ) + assert "✅ Finalized: Successfully unstaked all stakes from" in unstake_all.stdout + print("Passed unstaking tests 🎉") From c2d7e7ceba5ca832af6ec34f5773a178342ded6d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 16:58:55 -0800 Subject: [PATCH 14/33] Cleanup --- bittensor_cli/cli.py | 4 +--- tests/e2e_tests/test_unstaking.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 76be6debe..c04e33ba8 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3234,10 +3234,8 @@ def stake_remove( "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from [dim](or enter 'all' to unstake from all hotkeys)[/dim]", default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, ) - elif hotkey_ss58_address: - hotkey_or_ss58 = hotkey_ss58_address else: - hotkey_or_ss58 = wallet_hotkey + hotkey_or_ss58 = hotkey_ss58_address or wallet_hotkey if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py index c401654e5..b885d8adf 100644 --- a/tests/e2e_tests/test_unstaking.py +++ b/tests/e2e_tests/test_unstaking.py @@ -97,7 +97,7 @@ def test_unstaking(local_chain, wallet_setup): ) assert "✅ Registered subnetwork with netuid: 3" in result.stdout - # Register Bob in both subnets + # Register Bob in one subnet register_result = exec_command_bob( command="subnets", sub_command="register", From 8031834ffbc8f76fd54c2df494dd07e69389e8de Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 18 Feb 2025 17:32:21 -0800 Subject: [PATCH 15/33] removes outdated calculation + adds swap value --- bittensor_cli/src/commands/stake/list.py | 81 ++++++------------------ 1 file changed, 18 insertions(+), 63 deletions(-) diff --git a/bittensor_cli/src/commands/stake/list.py b/bittensor_cli/src/commands/stake/list.py index 6c2feff6c..8523320ac 100644 --- a/bittensor_cli/src/commands/stake/list.py +++ b/bittensor_cli/src/commands/stake/list.py @@ -1,7 +1,6 @@ import asyncio from typing import TYPE_CHECKING, Optional -from bittensor_cli.src.commands.stake.remove import unstake import typer from bittensor_wallet import Wallet @@ -59,7 +58,6 @@ async def get_stake_data(block_hash: str = None): def define_table( hotkey_name: str, rows: list[list[str]], - total_tao_ownership: Balance, total_tao_value: Balance, total_swapped_tao_value: Balance, live: bool = False, @@ -145,7 +143,6 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): else hotkey_ ) rows = [] - total_tao_ownership = Balance(0) total_tao_value = Balance(0) total_swapped_tao_value = Balance(0) root_stakes = [s for s in substakes if s.netuid == 0] @@ -161,14 +158,6 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): netuid = substake_.netuid pool = dynamic_info[netuid] symbol = f"{Balance.get_unit(netuid)}\u200e" - # TODO: what is this price var for? - price = ( - "{:.4f}{}".format( - pool.price.__float__(), f" τ/{Balance.get_unit(netuid)}\u200e" - ) - if pool.is_dynamic - else (f" 1.0000 τ/{symbol} ") - ) # Alpha value cell alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid) @@ -198,31 +187,11 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): else f"{swapped_tao_value} ({slippage_percentage})" ) - # TAO locked cell - tao_locked = pool.tao_in - - # Issuance cell - issuance = pool.alpha_out if pool.is_dynamic else tao_locked - # Per block emission cell per_block_emission = substake_.emission.tao / (pool.tempo or 1) per_block_tao_emission = substake_.tao_emission.tao / (pool.tempo or 1) # Alpha ownership and TAO ownership cells if alpha_value.tao > 0.00009: - if issuance.tao != 0: - # TODO figure out why this alpha_ownership does nothing - alpha_ownership = "{:.4f}".format( - (alpha_value.tao / issuance.tao) * 100 - ) - tao_ownership = Balance.from_tao( - (alpha_value.tao / issuance.tao) * tao_locked.tao - ) - total_tao_ownership += tao_ownership - else: - # TODO what's this var for? - alpha_ownership = "0.0000" - tao_ownership = Balance.from_tao(0) - stake_value = ( millify_tao(substake_.stake.tao) if not verbose @@ -253,13 +222,11 @@ def create_table(hotkey_: str, substakes: list[StakeInfo]): str(Balance.from_tao(per_block_tao_emission)), ] ) - table = define_table( - name, rows, total_tao_ownership, total_tao_value, total_swapped_tao_value - ) + table = define_table(name, rows, total_tao_value, total_swapped_tao_value) for row in rows: table.add_row(*row) console.print(table) - return total_tao_ownership, total_tao_value + return total_tao_value, total_swapped_tao_value def create_live_table( substakes: list, @@ -271,7 +238,6 @@ def create_live_table( rows = [] current_data = {} - total_tao_ownership = Balance(0) total_tao_value = Balance(0) total_swapped_tao_value = Balance(0) @@ -332,17 +298,6 @@ def format_cell( ) total_swapped_tao_value += swapped_tao_value - # Calculate TAO ownership - tao_locked = pool.tao_in - issuance = pool.alpha_out if pool.is_dynamic else tao_locked - if alpha_value.tao > 0.00009 and issuance.tao != 0: - tao_ownership = Balance.from_tao( - (alpha_value.tao / issuance.tao) * tao_locked.tao - ) - total_tao_ownership += tao_ownership - else: - tao_ownership = Balance.from_tao(0) - # Store current values for future delta tracking current_data[netuid] = { "stake": alpha_value.tao, @@ -350,8 +305,7 @@ def format_cell( "tao_value": tao_value.tao, "swapped_value": swapped_tao_value.tao, "emission": substake.emission.tao / (pool.tempo or 1), - "tao_ownership": tao_ownership.tao, - "tao_emission": substake.tao_emission.tao (pool.tempo or 1), + "tao_emission": substake.tao_emission.tao / (pool.tempo or 1), } # Get previous values for delta tracking @@ -442,7 +396,6 @@ def format_cell( table = define_table( hotkey_name, rows, - total_tao_ownership, total_tao_value, total_swapped_tao_value, live=True, @@ -478,7 +431,7 @@ def format_cell( raise typer.Exit() if live: - # Select one hokkey for live monitoring + # Select one hotkey for live monitoring if len(hotkeys_to_substakes) > 1: console.print( "\n[bold]Multiple hotkeys found. Please select one for live monitoring:[/bold]" @@ -582,27 +535,29 @@ def format_cell( # Iterate over each hotkey and make a table counter = 0 num_hotkeys = len(hotkeys_to_substakes) - all_hotkeys_total_global_tao = Balance(0) - all_hotkeys_total_tao_value = Balance(0) + all_hks_swapped_tao_value = Balance(0) + all_hks_tao_value = Balance(0) for hotkey in hotkeys_to_substakes.keys(): counter += 1 - stake, value = create_table(hotkey, hotkeys_to_substakes[hotkey]) - all_hotkeys_total_global_tao += stake - all_hotkeys_total_tao_value += value + tao_value, swapped_tao_value = create_table( + hotkey, hotkeys_to_substakes[hotkey] + ) + all_hks_tao_value += tao_value + all_hks_swapped_tao_value += swapped_tao_value if num_hotkeys > 1 and counter < num_hotkeys and prompt: console.print("\nPress Enter to continue to the next hotkey...") input() total_tao_value = ( - f"τ {millify_tao(all_hotkeys_total_tao_value.tao)}" + f"τ {millify_tao(all_hks_tao_value.tao)}" if not verbose - else all_hotkeys_total_tao_value + else all_hks_tao_value ) - total_tao_ownership = ( - f"τ {millify_tao(all_hotkeys_total_global_tao.tao)}" + total_swapped_tao_value = ( + f"τ {millify_tao(all_hks_swapped_tao_value.tao)}" if not verbose - else all_hotkeys_total_global_tao + else all_hks_swapped_tao_value ) console.print("\n\n") @@ -610,8 +565,8 @@ def format_cell( f"Wallet:\n" f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n" f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" - f" Total TAO ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_ownership}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" - f" Total Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" + f" Total TAO Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n" + f" Total TAO Swapped Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_swapped_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]" ) if not sub_stakes: console.print( From 0d223010930fc694ce85b66ff2d49638db003531 Mon Sep 17 00:00:00 2001 From: ashik08 Date: Wed, 19 Feb 2025 12:32:14 -0600 Subject: [PATCH 16/33] fix: remove double conversion in stake swap functionality [--swap_all] --- bittensor_cli/src/commands/stake/move.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/stake/move.py b/bittensor_cli/src/commands/stake/move.py index 3197d395e..964f0ed1b 100644 --- a/bittensor_cli/src/commands/stake/move.py +++ b/bittensor_cli/src/commands/stake/move.py @@ -905,7 +905,7 @@ async def swap_stake( ) if swap_all: - amount_to_swap = Balance.from_tao(current_stake).set_unit(origin_netuid) + amount_to_swap = current_stake.set_unit(origin_netuid) else: amount_to_swap = Balance.from_tao(amount).set_unit(origin_netuid) From d31fd04a7b845c11fd29d60e705e2a65c26f7694 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 19 Feb 2025 21:14:27 +0200 Subject: [PATCH 17/33] Adds better helper text for mnemonic option + ruff. --- bittensor_cli/cli.py | 8 +++++--- bittensor_cli/src/commands/stake/add.py | 8 ++------ bittensor_cli/src/commands/subnets/subnets.py | 8 +++----- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 70ff7bd52..fd58425f1 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -124,7 +124,7 @@ class Options: ) mnemonic = typer.Option( None, - help="Mnemonic used to regenerate your key. For example: horse cart dog ...", + help='Mnemonic used to regenerate your key. For example: "horse cart dog ..."', ) seed = typer.Option( None, help="Seed hex string used to regenerate your key. For example: 0x1234..." @@ -2130,6 +2130,7 @@ def wallet_regen_hotkey( # Example usage: [green]$[/green] btcli wallet regen_hotkey --seed 0x1234... + [green]$[/green] btcli wallet regen-hotkey --mnemonic "word1 word2 ... word12" [bold]Note[/bold]: This command is essential for users who need to regenerate their hotkey, possibly for security upgrades or key recovery. It should be used with caution to avoid accidental overwriting of existing keys. @@ -3229,10 +3230,11 @@ def stake_remove( ask_for=[WO.NAME, WO.PATH], ) else: - if not hotkey_ss58_address and not wallet_hotkey: + if not hotkey_ss58_address and not wallet_hotkey: hotkey_or_ss58 = Prompt.ask( "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from [dim](or enter 'all' to unstake from all hotkeys)[/dim]", - default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + default=self.config.get("wallet_hotkey") + or defaults.wallet.hotkey, ) else: hotkey_or_ss58 = hotkey_ss58_address or wallet_hotkey diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index cbc1ae242..4aba39e69 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -107,9 +107,7 @@ async def safe_stake_extrinsic( ) return else: - err_out( - f"\n{failure_prelude} with error: {format_error_message(e)}" - ) + err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return else: await response.process_events() @@ -180,9 +178,7 @@ async def stake_extrinsic( extrinsic, wait_for_inclusion=True, wait_for_finalization=False ) except SubstrateRequestException as e: - err_out( - f"\n{failure_prelude} with error: {format_error_message(e)}" - ) + err_out(f"\n{failure_prelude} with error: {format_error_message(e)}") return else: await response.process_events() diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index d71d726f8..f4e01486c 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -889,8 +889,8 @@ async def show_root(): total_emission_per_block = 0 for netuid_ in range(len(all_subnets)): subnet = all_subnets[netuid_] - emission_on_subnet = ( - root_state.emission_history[netuid_][idx] / (subnet.tempo or 1) + emission_on_subnet = root_state.emission_history[netuid_][idx] / ( + subnet.tempo or 1 ) total_emission_per_block += subnet.alpha_to_tao( Balance.from_rao(emission_on_subnet) @@ -2135,9 +2135,7 @@ async def get_identity(subtensor: "SubtensorInterface", netuid: int, title: str title = "Subnet Identity" if not await subtensor.subnet_exists(netuid): - print_error( - f"Subnet {netuid} does not exist." - ) + print_error(f"Subnet {netuid} does not exist.") raise typer.Exit() with console.status( From b8ae46e463d9a8b22e20d876838dc2b091a9887d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 19 Feb 2025 22:12:45 +0200 Subject: [PATCH 18/33] Made it actually work. --- bittensor_cli/cli.py | 5 ++-- bittensor_cli/src/commands/subnets/subnets.py | 4 +-- bittensor_cli/src/commands/sudo.py | 28 +++++++++++-------- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 70ff7bd52..012926c0b 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3229,10 +3229,11 @@ def stake_remove( ask_for=[WO.NAME, WO.PATH], ) else: - if not hotkey_ss58_address and not wallet_hotkey: + if not hotkey_ss58_address and not wallet_hotkey: hotkey_or_ss58 = Prompt.ask( "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from [dim](or enter 'all' to unstake from all hotkeys)[/dim]", - default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + default=self.config.get("wallet_hotkey") + or defaults.wallet.hotkey, ) else: hotkey_or_ss58 = hotkey_ss58_address or wallet_hotkey diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index 39ad29448..f4e01486c 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -889,8 +889,8 @@ async def show_root(): total_emission_per_block = 0 for netuid_ in range(len(all_subnets)): subnet = all_subnets[netuid_] - emission_on_subnet = ( - root_state.emission_history[netuid_][idx] / (subnet.tempo or 1) + emission_on_subnet = root_state.emission_history[netuid_][idx] / ( + subnet.tempo or 1 ) total_emission_per_block += subnet.alpha_to_tao( Balance.from_rao(emission_on_subnet) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index ca1eb7e5c..446e80712 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -111,16 +111,19 @@ def type_converter_with_retry(type_, val, arg_name): if call.name == param_name: if "netuid" not in [x.name for x in call.args]: return False, None - call_args = [arg for arg in call.args if arg.name != "netuid"] + call_args = [ + arg for arg in call.args if arg.value["name"] != "netuid" + ] if len(call_args) == 1: - arg = call_args[0] - call_crafter[arg.name] = type_converter_with_retry( - arg.typeName, value, arg.name + arg = call_args[0].value + call_crafter[arg["name"]] = type_converter_with_retry( + arg["typeName"], value, arg["name"] ) else: - for arg in call_args: - call_crafter[arg.name] = type_converter_with_retry( - arg.typeName, None, arg.name + for arg_ in call_args: + arg = arg_.value + call_crafter[arg["name"]] = type_converter_with_retry( + arg["typeName"], None, arg["name"] ) return True, call_crafter else: @@ -170,10 +173,11 @@ async def set_hyperparameter_extrinsic( arbitrary_extrinsic = False extrinsic, sudo_ = HYPERPARAMS.get(parameter, ("", False)) - if extrinsic is None: + if not extrinsic: arbitrary_extrinsic, call_params = search_metadata( parameter, value, netuid, subtensor.substrate.metadata ) + extrinsic = parameter if not arbitrary_extrinsic: err_console.print( ":cross_mark: [red]Invalid hyperparameter specified.[/red]" @@ -184,8 +188,8 @@ async def set_hyperparameter_extrinsic( f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{value}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...", spinner="earth", ): + substrate = subtensor.substrate if not arbitrary_extrinsic: - substrate = subtensor.substrate extrinsic_params = await substrate.get_metadata_call_function( "AdminUtils", extrinsic ) @@ -554,7 +558,10 @@ async def sudo_set_hyperparameter( ]: normalized_value = param_value.lower() in ["true", "1"] elif param_value in ("True", "False"): - normalized_value = bool(param_value) + normalized_value = { + "True": True, + "False": False, + }[param_value] else: normalized_value = param_value @@ -565,7 +572,6 @@ async def sudo_set_hyperparameter( f"Value is {normalized_value} but must be {value}" ) return - success = await set_hyperparameter_extrinsic( subtensor, wallet, netuid, param_name, value ) From 7a0c8e40ea9a53ae9bd471c6961dfbc6374c8eb7 Mon Sep 17 00:00:00 2001 From: Igor Date: Wed, 19 Feb 2025 14:34:57 -0600 Subject: [PATCH 19/33] fix feedback --- bittensor_cli/__init__.py | 20 +------------------- bittensor_cli/cli.py | 6 ++---- bittensor_cli/version.py | 18 ++++++++++++++++++ setup.py | 2 +- 4 files changed, 22 insertions(+), 24 deletions(-) create mode 100644 bittensor_cli/version.py diff --git a/bittensor_cli/__init__.py b/bittensor_cli/__init__.py index 5d9f88240..c3f0e0102 100644 --- a/bittensor_cli/__init__.py +++ b/bittensor_cli/__init__.py @@ -16,25 +16,7 @@ # DEALINGS IN THE SOFTWARE. from .cli import CLIManager - -def version_as_int(version): - import re - _core_version = re.match(r"^\d+\.\d+\.\d+", version).group(0) - _version_split = _core_version.split(".") - __version_info__ = tuple(int(part) for part in _version_split) - _version_int_base = 1000 - assert max(__version_info__) < _version_int_base - - __version_as_int__: int = sum( - e * (_version_int_base**i) for i, e in enumerate(reversed(__version_info__)) - ) - assert __version_as_int__ < 2**31 # fits in int32 - __new_signature_version__ = 360 - return __version_as_int__ - -__version__ = "9.0.0" -__version_as_int__ = version_as_int(__version__) - +from .version import __version__, __version_as_int__ __all__ = ["CLIManager", "__version__", "__version_as_int__"] diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0c7b1241d..27d3ed54b 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -27,6 +27,7 @@ Constants, COLOR_PALETTE, ) +from bittensor_cli.version import __version__, __version_as_int__ from bittensor_cli.src.bittensor import utils from bittensor_cli.src.bittensor.balances import Balance from async_substrate_interface.errors import SubstrateRequestException @@ -471,8 +472,6 @@ def version_callback(value: bool): """ Prints the current version/branch-name """ - from bittensor_cli import __version__ - if value: try: repo = Repo(os.path.dirname(os.path.dirname(__file__))) @@ -4904,7 +4903,6 @@ def weights_reveal( validate=WV.WALLET_AND_HOTKEY, ) - from bittensor_cli import __version_as_int__ return self._run_command( weights_cmds.reveal_weights( self.initialize_chain(network), @@ -5002,7 +5000,7 @@ def weights_commit( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) - from bittensor_cli import __version_as_int__ + return self._run_command( weights_cmds.commit_weights( self.initialize_chain(network), diff --git a/bittensor_cli/version.py b/bittensor_cli/version.py new file mode 100644 index 000000000..b47664c90 --- /dev/null +++ b/bittensor_cli/version.py @@ -0,0 +1,18 @@ +import re + +def version_as_int(version): + _core_version = re.match(r"^\d+\.\d+\.\d+", version).group(0) + _version_split = _core_version.split(".") + __version_info__ = tuple(int(part) for part in _version_split) + _version_int_base = 1000 + assert max(__version_info__) < _version_int_base + + __version_as_int__: int = sum( + e * (_version_int_base**i) for i, e in enumerate(reversed(__version_info__)) + ) + assert __version_as_int__ < 2**31 # fits in int32 + __new_signature_version__ = 360 + return __version_as_int__ + +__version__ = "9.0.0" +__version_as_int__ = version_as_int(__version__) diff --git a/setup.py b/setup.py index d827cb4b8..45bfc0fa4 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def read_requirements(path): # loading version from setup.py with codecs.open( - os.path.join(here, "bittensor_cli/__init__.py"), encoding="utf-8" + os.path.join(here, "bittensor_cli/version.py"), encoding="utf-8" ) as init_file: version_match = re.search( r"^__version__ = ['\"]([^'\"]*)['\"]", init_file.read(), re.M From b1ec1446d232499408814699ba680d8bc00ea4c6 Mon Sep 17 00:00:00 2001 From: igorsyl Date: Wed, 19 Feb 2025 14:42:46 -0600 Subject: [PATCH 20/33] fix feedback --- bittensor_cli/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ccf6bcb3d..da8f8ffda 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2589,7 +2589,6 @@ def wallet_set_id( additional, github_repo, ) - return self._run_command( wallets.set_id( wallet, From 2bb818cbeb6b17cf43793b97e37eca5905c34a67 Mon Sep 17 00:00:00 2001 From: igorsyl Date: Wed, 19 Feb 2025 14:43:20 -0600 Subject: [PATCH 21/33] fix feedback --- bittensor_cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index da8f8ffda..f5e0cf901 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2589,6 +2589,7 @@ def wallet_set_id( additional, github_repo, ) + return self._run_command( wallets.set_id( wallet, @@ -5021,7 +5022,6 @@ def weights_commit( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) - return self._run_command( weights_cmds.commit_weights( self.initialize_chain(network), From 3bd79b81325084443a8b4d22d653006c91a99c32 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 19 Feb 2025 22:59:09 +0200 Subject: [PATCH 22/33] Update "network_registration_allowed" to match the SubnetHyperparameters "registration_allowed" --- bittensor_cli/src/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 0a6d09a3a..e01563096 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -672,7 +672,7 @@ class WalletValidationTypes(Enum): "commit_reveal_weights_enabled": ("sudo_set_commit_reveal_weights_enabled", False), "alpha_values": ("sudo_set_alpha_values", False), "liquid_alpha_enabled": ("sudo_set_liquid_alpha_enabled", False), - "network_registration_allowed": ("sudo_set_network_registration_allowed", False), + "registration_allowed": ("sudo_set_network_registration_allowed", False), "network_pow_registration_allowed": ( "sudo_set_network_pow_registration_allowed", False, From 87034b25bd24d958658254484e89d053d4877bcb Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 19 Feb 2025 23:40:33 +0200 Subject: [PATCH 23/33] Readability improved --- bittensor_cli/cli.py | 12 ++++++++---- bittensor_cli/src/commands/sudo.py | 8 ++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 012926c0b..32d76d737 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -26,6 +26,7 @@ WalletValidationTypes as WV, Constants, COLOR_PALETTE, + HYPERPARAMS, ) from bittensor_cli.src.bittensor import utils from bittensor_cli.src.bittensor.balances import Balance @@ -3984,7 +3985,7 @@ def sudo_set( param_name: str = typer.Option( "", "--param", "--parameter", help="The subnet hyperparameter to set" ), - param_value: str = typer.Option( + param_value: Optional[str] = typer.Option( "", "--value", help="Value to set the hyperparameter to." ), quiet: bool = Options.quiet, @@ -4033,9 +4034,12 @@ def sudo_set( param_value = f"{low_val},{high_val}" if not param_value: - param_value = Prompt.ask( - f"Enter the new value for [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{param_name}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] in the VALUE column format" - ) + if HYPERPARAMS.get(param_name): + param_value = Prompt.ask( + f"Enter the new value for [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{param_name}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] in the VALUE column format" + ) + else: + param_value = None wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 446e80712..77c664abc 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -184,11 +184,15 @@ async def set_hyperparameter_extrinsic( ) return False + substrate = subtensor.substrate + msg_value = value if not arbitrary_extrinsic else call_params with console.status( - f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{value}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...", + f":satellite: Setting hyperparameter [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{parameter}" + f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{msg_value}" + f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] on subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]" + f"{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] ...", spinner="earth", ): - substrate = subtensor.substrate if not arbitrary_extrinsic: extrinsic_params = await substrate.get_metadata_call_function( "AdminUtils", extrinsic From 1a91049caebf3cc15f71e2ca56f18c27b01d089c Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 19 Feb 2025 23:42:32 +0200 Subject: [PATCH 24/33] Typing. --- bittensor_cli/src/commands/sudo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 77c664abc..f997a572f 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -135,7 +135,7 @@ async def set_hyperparameter_extrinsic( wallet: "Wallet", netuid: int, parameter: str, - value: Union[str, bool, float, list[float]], + value: Optional[Union[str, bool, float, list[float]]], wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> bool: @@ -549,7 +549,7 @@ async def sudo_set_hyperparameter( subtensor: "SubtensorInterface", netuid: int, param_name: str, - param_value: str, + param_value: Optional[str], ): """Set subnet hyperparameters.""" From ebc24044c83ee0eadd58907d68d2670725048721 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 20 Feb 2025 00:11:41 +0200 Subject: [PATCH 25/33] String to bool conversion. --- bittensor_cli/src/commands/sudo.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index f997a572f..4026138a7 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -90,6 +90,12 @@ def search_metadata( """ + def string_to_bool(val) -> bool: + try: + return {"true": True, "1": True, "0": False, "false": False}[val.lower()] + except KeyError: + return ValueError + def type_converter_with_retry(type_, val, arg_name): try: if val is None: @@ -100,8 +106,8 @@ def type_converter_with_retry(type_, val, arg_name): except ValueError: return type_converter_with_retry(type_, None, arg_name) - arg_types = {"bool": bool, "u16": float_to_u16, "u64": float_to_u64} - arg_type_output = {"bool": bool, "u16": float, "u64": float} + arg_types = {"bool": string_to_bool, "u16": float_to_u16, "u64": float_to_u64} + arg_type_output = {"bool": "bool", "u16": "float", "u64": "float"} call_crafter = {"netuid": netuid} From a88531d70209064c51848cc2bb51b310c1e8f7fc Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 20 Feb 2025 00:13:15 +0200 Subject: [PATCH 26/33] Spacing --- bittensor_cli/src/commands/sudo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 4026138a7..78106ae31 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -100,7 +100,7 @@ def type_converter_with_retry(type_, val, arg_name): try: if val is None: val = input( - f"Enter a value for field '{arg_name}' with type '{arg_type_output[type_]}'" + f"Enter a value for field '{arg_name}' with type '{arg_type_output[type_]}': " ) return arg_types[type_](val) except ValueError: From d8b9ef763247da300992d791faa569a3cd8bec87 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 19 Feb 2025 16:48:57 -0800 Subject: [PATCH 27/33] Bumps deps for btcli --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 521e94944..1080ed7ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ wheel async-property==0.2.2 -async-substrate-interface>=1.0.0 +async-substrate-interface>=1.0.2 aiohttp~=3.10.2 backoff~=2.2.1 GitPython>=3.0.0 @@ -16,7 +16,7 @@ rich~=13.7 scalecodec==1.2.11 typer~=0.12 websockets>=14.1 -bittensor-wallet>=3.0.3 +bittensor-wallet>=3.0.4 plotille pywry plotly \ No newline at end of file From be092f53b624b22a029624ff47c62c2baf358410 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 20 Feb 2025 14:54:32 +0200 Subject: [PATCH 28/33] Refactor the logic for the SubtensorInterface.__aenter__ so that it doesn't close the connection after initialisation. --- bittensor_cli/cli.py | 5 +++-- bittensor_cli/src/bittensor/subtensor_interface.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 70ff7bd52..012926c0b 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3229,10 +3229,11 @@ def stake_remove( ask_for=[WO.NAME, WO.PATH], ) else: - if not hotkey_ss58_address and not wallet_hotkey: + if not hotkey_ss58_address and not wallet_hotkey: hotkey_or_ss58 = Prompt.ask( "Enter the [blue]hotkey[/blue] name or [blue]ss58 address[/blue] to unstake all from [dim](or enter 'all' to unstake from all hotkeys)[/dim]", - default=self.config.get("wallet_hotkey") or defaults.wallet.hotkey, + default=self.config.get("wallet_hotkey") + or defaults.wallet.hotkey, ) else: hotkey_or_ss58 = hotkey_ss58_address or wallet_hotkey diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index ff687fe1b..65474b324 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -112,8 +112,8 @@ async def __aenter__(self): f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..." ): try: - async with self.substrate: - return self + await self.substrate.initialize() + return self except TimeoutError: # TODO verify err_console.print( "\n[red]Error[/red]: Timeout occurred connecting to substrate. " From 25c2acb27a7a7bf0559678baf535d5959827bf8d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 20 Feb 2025 09:54:00 -0800 Subject: [PATCH 29/33] Bumps requirements --- bittensor_cli/cli.py | 11 +++++++++-- requirements.txt | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 35876070e..656eda6b7 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 import asyncio +import cProfile import curses import importlib import os.path +import pstats import re import ssl import sys @@ -978,8 +980,13 @@ async def _run(): except Exception as e: # ensures we always exit cleanly if not isinstance(e, (typer.Exit, RuntimeError)): err_console.print(f"An unknown error has occurred: {e}") - - return self.asyncio_runner(_run()) + profiler = cProfile.Profile() + profiler.enable() + result = self.asyncio_runner(_run()) + profiler.disable() + stats = pstats.Stats(profiler).sort_stats('cumulative') + stats.print_stats(20) # Show top 20 time-consuming operations + return result def main_callback( self, diff --git a/requirements.txt b/requirements.txt index 1080ed7ed..7893040e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ wheel async-property==0.2.2 -async-substrate-interface>=1.0.2 +async-substrate-interface>=1.0.3 aiohttp~=3.10.2 backoff~=2.2.1 GitPython>=3.0.0 From 34ab9e5d00d3e7bfb4c78d08809d83057c95393b Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 20 Feb 2025 09:55:51 -0800 Subject: [PATCH 30/33] cleanup --- bittensor_cli/cli.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 656eda6b7..bf9672985 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -980,13 +980,8 @@ async def _run(): except Exception as e: # ensures we always exit cleanly if not isinstance(e, (typer.Exit, RuntimeError)): err_console.print(f"An unknown error has occurred: {e}") - profiler = cProfile.Profile() - profiler.enable() - result = self.asyncio_runner(_run()) - profiler.disable() - stats = pstats.Stats(profiler).sort_stats('cumulative') - stats.print_stats(20) # Show top 20 time-consuming operations - return result + + return self.asyncio_runner(_run()) def main_callback( self, From fa4008d65ee6e0f1097e17adf622b0716eb565de Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 20 Feb 2025 09:56:22 -0800 Subject: [PATCH 31/33] more cleanup --- bittensor_cli/cli.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index bf9672985..e144d0cb5 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import asyncio -import cProfile import curses import importlib import os.path -import pstats import re import ssl import sys From a962a1dc1ec2d53f629d20b5054175d0b8554cb6 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 20 Feb 2025 09:56:44 -0800 Subject: [PATCH 32/33] ruff --- bittensor_cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index e144d0cb5..35876070e 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -978,7 +978,7 @@ async def _run(): except Exception as e: # ensures we always exit cleanly if not isinstance(e, (typer.Exit, RuntimeError)): err_console.print(f"An unknown error has occurred: {e}") - + return self.asyncio_runner(_run()) def main_callback( From 9692d50ed6680243e437d4f5bdcf93289185ebfb Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 20 Feb 2025 11:05:13 -0800 Subject: [PATCH 33/33] Bumps version and changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ bittensor_cli/version.py | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee709887..ed6f82e67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 9.0.2 /2025-02-20 + +## What's Changed +* Fix stake child get by @thewhaleking in https://github.com/opentensor/btcli/pull/321 +* Edge case alpha formatting by @thewhaleking in https://github.com/opentensor/btcli/pull/318 +* Adds Tao emissions to stake list by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/300 +* Updates balance command by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/322 +* Backmerge main to staging 101 by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/326 +* Updates stake list (with swap value) by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/327 +* Adds unstaking from all hotkeys + tests by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/325 +* Mnemonic helper text by @thewhaleking in https://github.com/opentensor/btcli/pull/329 +* fix: remove double conversion in stake swap functionality [--swap_all] by @ashikshafi08 in https://github.com/opentensor/btcli/pull/328 +* Arbitrary Hyperparams Setting by @thewhaleking in https://github.com/opentensor/btcli/pull/320 +* Bumps deps for btcli by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/330 +* SubtensorInterface async with logic by @thewhaleking in https://github.com/opentensor/btcli/pull/331 +* remove __version__ from cli.py by @igorsyl in https://github.com/opentensor/btcli/pull/323 + +## New Contributors +* @ashikshafi08 made their first contribution in https://github.com/opentensor/btcli/pull/328 +* @igorsyl made their first contribution in https://github.com/opentensor/btcli/pull/323 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.0.1...v9.0.2 + ## 9.0.1 /2025-02-13 ## What's Changed diff --git a/bittensor_cli/version.py b/bittensor_cli/version.py index b47664c90..750e8ad1b 100644 --- a/bittensor_cli/version.py +++ b/bittensor_cli/version.py @@ -14,5 +14,5 @@ def version_as_int(version): __new_signature_version__ = 360 return __version_as_int__ -__version__ = "9.0.0" +__version__ = "9.0.2" __version_as_int__ = version_as_int(__version__)