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/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 01ae3f619..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 @@ -719,11 +718,14 @@ 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. :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. """ @@ -750,13 +752,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 4c8560f83..0f919659b 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,37 +113,55 @@ 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", 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, @@ -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( @@ -581,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( @@ -625,8 +633,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 @@ -648,17 +657,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: 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")