From 04fded847f4a20b9c6160519f7a92a4ea68f4a23 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 9 Jun 2025 16:48:48 +0200 Subject: [PATCH 1/5] Adds Yuma 3 to sudo set and get for hyperparams --- bittensor_cli/src/__init__.py | 1 + bittensor_cli/src/bittensor/chain_data.py | 2 ++ .../src/bittensor/subtensor_interface.py | 16 ++++++++++------ bittensor_cli/src/commands/sudo.py | 5 +++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index ce55bfcd6..0f2bc3fc4 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -658,6 +658,7 @@ class WalletValidationTypes(Enum): "sudo_set_network_pow_registration_allowed", False, ), + "yuma3_enabled": ("sudo_set_yuma3_enabled", False), } # Help Panels for cli help diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 5fc29ba5f..6088b1a97 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -177,6 +177,7 @@ class SubnetHyperparameters(InfoBase): alpha_high: int alpha_low: int liquid_alpha_enabled: bool + yuma3_enabled: bool @classmethod def _fix_decoded( @@ -210,6 +211,7 @@ def _fix_decoded( alpha_high=decoded.get("alpha_high"), alpha_low=decoded.get("alpha_low"), liquid_alpha_enabled=decoded.get("liquid_alpha_enabled"), + yuma3_enabled=decoded.get("yuma3_enabled"), ) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 2bd0057bd..0d09f91dc 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -1128,14 +1128,18 @@ async def get_subnet_hyperparameters( Understanding the hyperparameters is crucial for comprehending how subnets are configured and managed, and how they interact with the network's consensus and incentive mechanisms. """ - result = await self.query_runtime_api( - runtime_api="SubnetInfoRuntimeApi", - method="get_subnet_hyperparams", - params=[netuid], - block_hash=block_hash, + main_result, yuma3_result = await asyncio.gather( + self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_hyperparams", + params=[netuid], + block_hash=block_hash, + ), + self.query("SubtensorModule", "Yuma3On", [netuid]), ) + result = {**main_result, **{"yuma3_enabled": yuma3_result}} - if not result: + if not main_result: return [] return SubnetHyperparameters.from_any(result) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 4c8560f83..08abe8ef7 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -625,8 +625,9 @@ async def get_hyperparameters( if not await subtensor.subnet_exists(netuid): print_error(f"Subnet with netuid {netuid} does not exist.") return False - subnet = await subtensor.get_subnet_hyperparameters(netuid) - subnet_info = await subtensor.subnet(netuid) + subnet, subnet_info = await asyncio.gather( + subtensor.get_subnet_hyperparameters(netuid), subtensor.subnet(netuid) + ) if subnet_info is None: print_error(f"Subnet with netuid {netuid} does not exist.") return False From b1f510d2ad2375489e5fefcbf5fe5be1a8aeaf4d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 9 Jun 2025 18:18:30 +0200 Subject: [PATCH 2/5] Adds tests for yuma3 enabled --- tests/e2e_tests/test_staking_sudo.py | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/e2e_tests/test_staking_sudo.py b/tests/e2e_tests/test_staking_sudo.py index d3e566c76..904df53ce 100644 --- a/tests/e2e_tests/test_staking_sudo.py +++ b/tests/e2e_tests/test_staking_sudo.py @@ -461,4 +461,51 @@ def test_staking(local_chain, wallet_setup): )["value"] assert Balance.from_rao(max_burn_tao_from_json) == Balance.from_tao(10.0) + change_yuma3_hyperparam = exec_command_alice( + command="sudo", + sub_command="set", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--chain", + "ws://127.0.0.1:9945", + "--netuid", + netuid, + "--param", + "yuma3_enabled", + "--value", + "true", + "--no-prompt", + "--json-output", + ], + ) + change_yuma3_hyperparam_json = json.loads(change_yuma3_hyperparam.stdout) + assert change_yuma3_hyperparam_json["success"] is True, ( + change_yuma3_hyperparam.stdout + ) + + changed_yuma3_hyperparam = exec_command_alice( + command="sudo", + sub_command="get", + extra_args=[ + "--chain", + "ws://127.0.0.1:9945", + "--netuid", + netuid, + "--json-output", + ], + ) + + yuma3_val = next( + filter( + lambda x: x["hyperparameter"] == "yuma3_enabled", + json.loads(changed_yuma3_hyperparam.stdout), + ) + ) + assert yuma3_val["value"] is True + assert yuma3_val["normalized_value"] is True print("✅ Passed staking and sudo commands") From 40d81d1cdddb41c67af2b773cb4c92be2fd48ddb Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 9 Jun 2025 18:19:02 +0200 Subject: [PATCH 3/5] Bug fixes + optmisation --- bittensor_cli/src/bittensor/utils.py | 9 ++- bittensor_cli/src/commands/sudo.py | 104 +++++++++++++++++---------- 2 files changed, 72 insertions(+), 41 deletions(-) diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 01ae3f619..3aede7c2d 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -719,6 +719,7 @@ def millify_tao(n: float, start_at: str = "K") -> str: def normalize_hyperparameters( subnet: "SubnetHyperparameters", + json_output: bool = False, ) -> list[tuple[str, str, str]]: """ Normalizes the hyperparameters of a subnet. @@ -750,13 +751,17 @@ def normalize_hyperparameters( norm_value = param_mappings[param](value) if isinstance(norm_value, float): norm_value = f"{norm_value:.{10}g}" + if isinstance(norm_value, Balance) and json_output: + norm_value = norm_value.to_dict() else: norm_value = value except Exception: # bittensor.logging.warning(f"Error normalizing parameter '{param}': {e}") norm_value = "-" - - normalized_values.append((param, str(value), str(norm_value))) + if not json_output: + normalized_values.append((param, str(value), str(norm_value))) + else: + normalized_values.append((param, value, norm_value)) return normalized_values diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 08abe8ef7..0928f433d 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -73,6 +73,13 @@ def allowed_value( return True, value +def string_to_bool(val) -> bool: + try: + return {"true": True, "1": True, "0": False, "false": False}[val.lower()] + except KeyError: + return ValueError + + def search_metadata( param_name: str, value: Union[str, bool, float, list[float]], netuid: int, metadata ) -> tuple[bool, Optional[dict]]: @@ -91,12 +98,6 @@ 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: @@ -112,31 +113,49 @@ def type_converter_with_retry(type_, val, arg_name): 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 - call_args = [ - arg for arg in call.args if arg.value["name"] != "netuid" - ] - if len(call_args) == 1: - arg = call_args[0].value - call_crafter[arg["name"]] = type_converter_with_retry( - arg["typeName"], value, arg["name"] - ) - else: - 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 + pallet = metadata.get_metadata_pallet("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 + call_args = [arg for arg in call.args if arg.value["name"] != "netuid"] + if len(call_args) == 1: + arg = call_args[0].value + call_crafter[arg["name"]] = type_converter_with_retry( + arg["typeName"], value, arg["name"] + ) + else: + 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: return False, None +def requires_bool(metadata, param_name) -> bool: + """ + Determines whether a given hyperparam takes a single arg (besides netuid) that is of bool type. + """ + pallet = metadata.get_metadata_pallet("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 + call_args = [arg for arg in call.args if arg.value["name"] != "netuid"] + if len(call_args) != 1: + return False + else: + arg = call_args[0].value + if arg["typeName"] == "bool": + return True + else: + return False + raise ValueError(f"{param_name} not found in pallet.") + + async def set_hyperparameter_extrinsic( subtensor: "SubtensorInterface", wallet: "Wallet", @@ -221,15 +240,20 @@ async def set_hyperparameter_extrinsic( ] if len(value) < len(non_netuid_fields): - raise ValueError( + err_console.print( "Not enough values provided in the list for all parameters" ) + return False call_params.update( {str(name): val for name, val in zip(non_netuid_fields, value)} ) else: + if requires_bool( + substrate.metadata, param_name=extrinsic + ) and isinstance(value, str): + value = string_to_bool(value) value_argument = extrinsic_params["fields"][ len(extrinsic_params["fields"]) - 1 ] @@ -252,12 +276,13 @@ async def set_hyperparameter_extrinsic( ) if not success: err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") - await asyncio.sleep(0.5) + return False elif arbitrary_extrinsic: console.print( f":white_heavy_check_mark: " f"[dark_sea_green3]Hyperparameter {parameter} values changed to {call_params}[/dark_sea_green3]" ) + return True # Successful registration, final check for membership else: console.print( @@ -649,17 +674,18 @@ async def get_hyperparameters( ) dict_out = [] - normalized_values = normalize_hyperparameters(subnet) - + normalized_values = normalize_hyperparameters(subnet, json_output=json_output) for param, value, norm_value in normalized_values: - table.add_row(" " + param, value, norm_value) - dict_out.append( - { - "hyperparameter": param, - "value": value, - "normalized_value": norm_value, - } - ) + if not json_output: + table.add_row(" " + param, value, norm_value) + else: + dict_out.append( + { + "hyperparameter": param, + "value": value, + "normalized_value": norm_value, + } + ) if json_output: json_console.print(json.dumps(dict_out)) else: From fa64a6daba35b2df59c8319adac07a19ecbe2d0c Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 9 Jun 2025 18:20:22 +0200 Subject: [PATCH 4/5] Docstring --- bittensor_cli/src/bittensor/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 3aede7c2d..adf925edd 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -3,7 +3,6 @@ import math import os import sqlite3 -import platform import webbrowser from pathlib import Path from typing import TYPE_CHECKING, Any, Collection, Optional, Union, Callable @@ -725,6 +724,8 @@ def normalize_hyperparameters( Normalizes the hyperparameters of a subnet. :param subnet: The subnet hyperparameters object. + :param json_output: Whether this normalisation will be for a JSON output or console string (determines whether + items get stringified or safe for JSON encoding) :return: A list of tuples containing the parameter name, value, and normalized value. """ From d03a87ae34c43437d06d6082fd6d9e82e6f73f9b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 9 Jun 2025 18:37:48 +0200 Subject: [PATCH 5/5] Cleanup. --- bittensor_cli/src/commands/sudo.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 0928f433d..0f919659b 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -161,7 +161,7 @@ async def set_hyperparameter_extrinsic( wallet: "Wallet", netuid: int, parameter: str, - value: Optional[Union[str, bool, float, list[float]]], + value: Optional[Union[str, float, list[float]]], wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = True, @@ -606,28 +606,11 @@ async def sudo_set_hyperparameter( json_output: bool, ): """Set subnet hyperparameters.""" - - normalized_value: Union[str, bool] - if param_name in [ - "registration_allowed", - "network_pow_registration_allowed", - "commit_reveal_weights_enabled", - "liquid_alpha_enabled", - ]: - normalized_value = param_value.lower() in ["true", "1"] - elif param_value in ("True", "False"): - normalized_value = { - "True": True, - "False": False, - }[param_value] - else: - normalized_value = param_value - - is_allowed_value, value = allowed_value(param_name, normalized_value) + is_allowed_value, value = allowed_value(param_name, param_value) if not is_allowed_value: err_console.print( f"Hyperparameter [dark_orange]{param_name}[/dark_orange] value is not within bounds. " - f"Value is {normalized_value} but must be {value}" + f"Value is {param_value} but must be {value}" ) return False success = await set_hyperparameter_extrinsic(