diff --git a/CHANGELOG.md b/CHANGELOG.md index 660515c53..5aacd1c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 9.10.1 /2025-08-12 +* Removes double param for `--cache` in `config set` by @thewhaleking in https://github.com/opentensor/btcli/pull/579 +* change root only sudo hyperparams by @thewhaleking in https://github.com/opentensor/btcli/pull/580 +* Better error formatting by @thewhaleking in https://github.com/opentensor/btcli/pull/581 +* Handle optional netuid better by @thewhaleking in https://github.com/opentensor/btcli/pull/582 +* wallet fixes by @thewhaleking in https://github.com/opentensor/btcli/pull/585 +* Adds `moving_price` attr to DynamicInfo by @thewhaleking in https://github.com/opentensor/btcli/pull/583 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.10.0...v9.10.1 + ## 9.10.0 /2025-08-06 * Sets default interval hours for subnets price to 4, bc of rate limiting. by @thewhaleking in https://github.com/opentensor/btcli/pull/568 * Subnets Price --current + improvements by @thewhaleking in https://github.com/opentensor/btcli/pull/569 diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 65230eda6..6fddc03dd 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -410,10 +410,15 @@ def get_optional_netuid(netuid: Optional[int], all_netuids: bool) -> Optional[in ) if answer is None: return None + answer = answer.strip() if answer.lower() == "all": return None else: - return int(answer) + try: + return int(answer) + except ValueError: + err_console.print(f"Invalid netuid: {answer}") + return get_optional_netuid(None, False) else: return netuid @@ -1264,7 +1269,7 @@ def set_config( use_cache: Optional[bool] = typer.Option( None, "--cache/--no-cache", - "--cache/--no_cache", + " /--no_cache", help="Disable caching of some commands. This will disable the `--reuse-last` and `--html` flags on " "commands such as `subnets metagraph`, `stake show` and `subnets list`.", ), @@ -4816,7 +4821,7 @@ def sudo_set( wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) - result = self._run_command( + result, err_msg = self._run_command( sudo.sudo_set_hyperparameter( wallet, self.initialize_chain(network), @@ -4828,7 +4833,7 @@ def sudo_set( ) ) if json_output: - json_console.print(json.dumps({"success": result})) + json_console.print(json.dumps({"success": result, "err_msg": err_msg})) return result def sudo_get( diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index ebb01b433..7cd09ab84 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -631,10 +631,10 @@ class WalletValidationTypes(Enum): "min_allowed_weights": ("sudo_set_min_allowed_weights", False), "max_weights_limit": ("sudo_set_max_weight_limit", False), "tempo": ("sudo_set_tempo", True), - "min_difficulty": ("sudo_set_min_difficulty", False), + "min_difficulty": ("sudo_set_min_difficulty", True), "max_difficulty": ("sudo_set_max_difficulty", False), "weights_version": ("sudo_set_weights_version_key", False), - "weights_rate_limit": ("sudo_set_weights_set_rate_limit", False), + "weights_rate_limit": ("sudo_set_weights_set_rate_limit", True), "adjustment_interval": ("sudo_set_adjustment_interval", True), "activity_cutoff": ("sudo_set_activity_cutoff", False), "target_regs_per_interval": ("sudo_set_target_registrations_per_interval", True), @@ -645,7 +645,7 @@ class WalletValidationTypes(Enum): "serving_rate_limit": ("sudo_set_serving_rate_limit", False), "max_validators": ("sudo_set_max_allowed_validators", True), "adjustment_alpha": ("sudo_set_adjustment_alpha", False), - "difficulty": ("sudo_set_difficulty", False), + "difficulty": ("sudo_set_difficulty", True), "commit_reveal_period": ( "sudo_set_commit_reveal_weights_interval", False, @@ -653,7 +653,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), - "registration_allowed": ("sudo_set_network_registration_allowed", False), + "registration_allowed": ("sudo_set_network_registration_allowed", True), "network_pow_registration_allowed": ( "sudo_set_network_pow_registration_allowed", False, diff --git a/bittensor_cli/src/bittensor/chain_data.py b/bittensor_cli/src/bittensor/chain_data.py index 6fe7b3da7..0f64d8519 100644 --- a/bittensor_cli/src/bittensor/chain_data.py +++ b/bittensor_cli/src/bittensor/chain_data.py @@ -718,6 +718,7 @@ class DynamicInfo(InfoBase): network_registered_at: int subnet_identity: Optional[SubnetIdentity] subnet_volume: Balance + moving_price: float @classmethod def _fix_decoded(cls, decoded: Any) -> "DynamicInfo": @@ -786,6 +787,7 @@ def _fix_decoded(cls, decoded: Any) -> "DynamicInfo": network_registered_at=int(decoded.get("network_registered_at")), subnet_identity=subnet_identity, subnet_volume=subnet_volume, + moving_price=fixed_to_float(decoded["moving_price"], 32), ) def tao_to_alpha(self, tao: Balance) -> Balance: diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index d7ffa6e4a..8611db3f8 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -575,7 +575,9 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: err_type = error_message.get("type", err_type) err_name = error_message.get("name", err_name) err_docs = error_message.get("docs", [err_description]) - err_description = " ".join(err_docs) + err_description = ( + " ".join(err_docs) if not isinstance(err_docs, str) else err_docs + ) err_description += ( f" | Please consult {BT_DOCS_LINK}/errors/subtensor#{err_name.lower()}" ) @@ -1441,5 +1443,5 @@ def get_hotkey_pub_ss58(wallet: Wallet) -> str: """ try: return wallet.hotkeypub.ss58_address - except KeyFileError: + except (KeyFileError, AttributeError): return wallet.hotkey.ss58_address diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 1046c28cf..6d4ae13e8 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -177,7 +177,7 @@ async def set_hyperparameter_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = True, -) -> bool: +) -> tuple[bool, str]: """Sets a hyperparameter for a specific subnetwork. :param subtensor: initialized SubtensorInterface object @@ -200,13 +200,14 @@ async def set_hyperparameter_extrinsic( params=[netuid], ) if subnet_owner != wallet.coldkeypub.ss58_address: - err_console.print( + err_msg = ( ":cross_mark: [red]This wallet doesn't own the specified subnet.[/red]" ) - return False + err_console.print(err_msg) + return False, err_msg - if not unlock_key(wallet).success: - return False + if not (ulw := unlock_key(wallet)).success: + return False, ulw.message arbitrary_extrinsic = False @@ -218,15 +219,14 @@ async def set_hyperparameter_extrinsic( ) extrinsic = parameter if not arbitrary_extrinsic: - err_console.print( - ":cross_mark: [red]Invalid hyperparameter specified.[/red]" - ) - return False + err_msg = ":cross_mark: [red]Invalid hyperparameter specified.[/red]" + err_console.print(err_msg) + return False, err_msg if sudo_ and prompt: if not Confirm.ask( "This hyperparam is only settable by root sudo users. If you are not, this will fail. Please confirm" ): - return False + return False, "This hyperparam is only settable by root sudo users" substrate = subtensor.substrate msg_value = value if not arbitrary_extrinsic else call_params @@ -254,10 +254,11 @@ async def set_hyperparameter_extrinsic( ] if len(value) < len(non_netuid_fields): - err_console.print( + err_msg = ( "Not enough values provided in the list for all parameters" ) - return False + err_console.print(err_msg) + return False, err_msg call_params.update( {name: val for name, val in zip(non_netuid_fields, value)} @@ -290,20 +291,20 @@ async def set_hyperparameter_extrinsic( ) if not success: err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") - return False + return False, err_msg 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 + return True, "" # Successful registration, final check for membership else: console.print( f":white_heavy_check_mark: " f"[dark_sea_green3]Hyperparameter {parameter} changed to {value}[/dark_sea_green3]" ) - return True + return True, "" async def _get_senate_members( @@ -619,25 +620,26 @@ async def sudo_set_hyperparameter( param_value: Optional[str], prompt: bool, json_output: bool, -): +) -> tuple[bool, str]: """Set subnet hyperparameters.""" is_allowed_value, value = allowed_value(param_name, param_value) if not is_allowed_value: - err_console.print( + err_msg = ( f"Hyperparameter [dark_orange]{param_name}[/dark_orange] value is not within bounds. " f"Value is {param_value} but must be {value}" ) - return False - success = await set_hyperparameter_extrinsic( + err_console.print(err_msg) + return False, err_msg + success, err_msg = await set_hyperparameter_extrinsic( subtensor, wallet, netuid, param_name, value, prompt=prompt ) if json_output: - return success + return success, err_msg if success: console.print("\n") print_verbose("Fetching hyperparameters") await get_hyperparameters(subtensor, netuid=netuid) - return success + return success, err_msg async def get_hyperparameters( diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 00132d3d9..ff37a50e7 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -452,13 +452,15 @@ async def wallet_create( "error": "", "data": None, } + if uri: try: keypair = Keypair.create_from_uri(uri) - wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=False) - wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=False) - wallet.set_hotkey(keypair=keypair, encrypt=False, overwrite=False) - wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=False) + wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=overwrite) + wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=overwrite) + wallet.set_hotkey(keypair=keypair, encrypt=False, overwrite=overwrite) + wallet.set_hotkeypub(keypair=keypair, encrypt=False, overwrite=overwrite) + wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=overwrite) output_dict["success"] = True output_dict["data"] = { "name": wallet.name, diff --git a/pyproject.toml b/pyproject.toml index 5a32d2d55..767047b35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor-cli" -version = "9.10.0" +version = "9.10.1" description = "Bittensor CLI" readme = "README.md" authors = [ diff --git a/tests/e2e_tests/test_hyperparams_setting.py b/tests/e2e_tests/test_hyperparams_setting.py new file mode 100644 index 000000000..03d8616c3 --- /dev/null +++ b/tests/e2e_tests/test_hyperparams_setting.py @@ -0,0 +1,116 @@ +import json + +from bittensor_cli.src import HYPERPARAMS + +""" +Verify commands: + +* btcli subnets create +* btcli sudo set +* btcli sudo get +""" + + +def test_hyperparams_setting(local_chain, wallet_setup): + netuid = 2 + wallet_path_alice = "//Alice" + # Create wallet for Alice + keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( + wallet_path_alice + ) + + # Register a subnet with sudo as Alice + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--subnet-name", + "Test Subnet", + "--repo", + "https://github.com/username/repo", + "--contact", + "alice@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "alice#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Created by Alice", + "--logo-url", + "https://testsubnet.com/logo.png", + "--no-prompt", + "--json-output", + ], + ) + result_output = json.loads(result.stdout) + assert result_output["success"] is True + assert result_output["netuid"] == netuid + print(result_output) + + # Fetch the hyperparameters of the subnet + hyperparams = exec_command_alice( + command="sudo", + sub_command="get", + extra_args=[ + "--network", + "ws://127.0.0.1:9945", + "--netuid", + netuid, + "--json-out", + ], + ) + + # Parse all hyperparameters and single out max_burn in TAO + all_hyperparams = json.loads(hyperparams.stdout) + hp = {} + for hyperparam in all_hyperparams: + hp[hyperparam["hyperparameter"]] = hyperparam["value"] + for key, (_, sudo_only) in HYPERPARAMS.items(): + if key in hp.keys() and not sudo_only: + if isinstance(hp[key], bool): + new_val = not hp[key] + elif isinstance(hp[key], int): + if hp[key] < 100: + new_val = hp[key] + 1 + else: + new_val = hp[key] - 1 + else: + raise ValueError( + f"Unrecognized hyperparameter value type: {key}: {hp[key]}" + ) + cmd = exec_command_alice( + command="sudo", + sub_command="set", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--netuid", + netuid, + "--json-out", + "--no-prompt", + "--param", + key, + "--value", + new_val, + ], + ) + cmd_json = json.loads(cmd.stdout) + assert cmd_json["success"] is True, (key, new_val, cmd.stdout, cmd_json) + print(f"Successfully set hyperparameter {key} to value {new_val}") + print("Successfully set hyperparameters")