From d348fb3b6a8e4da30c5ba599480a4048c4ca24bf Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 17 Sep 2025 21:47:14 +0200 Subject: [PATCH 1/3] Initial implementation --- bittensor_cli/cli.py | 50 ++++++++++++++++++++++++++ bittensor_cli/src/__init__.py | 1 + bittensor_cli/src/commands/sudo.py | 56 ++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 88ae40580..d1cf71249 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -960,6 +960,9 @@ def __init__(self): self.sudo_app.command("get-take", rich_help_panel=HELP_PANELS["SUDO"]["TAKE"])( self.sudo_get_take ) + self.sudo_app.command("trim", rich_help_panel=HELP_PANELS["SUDO"]["CONFIG"])( + self.sudo_trim + ) # subnets commands self.subnets_app.command( @@ -5343,6 +5346,53 @@ def sudo_get_take( sudo.display_current_take(self.initialize_chain(network), wallet) ) + def sudo_trim( + self, + network: Optional[list[str]] = Options.network, + wallet_name: Optional[str] = Options.wallet_name, + wallet_path: Optional[str] = Options.wallet_path, + wallet_hotkey: Optional[str] = Options.wallet_hotkey, + netuid: int = Options.netuid, + max_uids: int = typer.Option( + None, + "--max", + "--max-uids", + help="The maximum number of allowed uids to which to trim", + prompt="Max UIDs", + ), + quiet: bool = Options.quiet, + verbose: bool = Options.verbose, + json_output: bool = Options.json_output, + prompt: bool = Options.prompt, + period: int = Options.period, + ): + """ + Allows subnet owners to trim UIDs on their subnet to a specified max number of netuids. + + EXAMPLE + [green]$[/green] btcli sudo trim --netuid 95 --wallet-name my_wallet --wallet-hotkey my_hotkey --max 6 + """ + self.verbosity_handler(quiet, verbose, json_output) + + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], + validate=WV.WALLET_AND_HOTKEY, + ) + self._run_command( + sudo.trim( + subtensor=self.initialize_chain(network), + wallet=wallet, + netuid=netuid, + max_n=max_uids, + period=period, + json_output=json_output, + prompt=prompt, + ) + ) + def subnets_list( self, network: Optional[list[str]] = Options.network, diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index ba96fe488..6ee781a46 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -666,6 +666,7 @@ class WalletValidationTypes(Enum): "user_liquidity_enabled": ("toggle_user_liquidity", False), "bonds_reset_enabled": ("sudo_set_bonds_reset_enabled", False), "transfers_enabled": ("sudo_set_toggle_transfer", False), + "min_allowed_uids": ("sudo_set_min_allowed_uids", True), } HYPERPARAMS_MODULE = { diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index e6ac31185..64c9cd202 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -955,3 +955,59 @@ async def _do_set_take() -> bool: result_ = await _do_set_take() return result_ + + +async def trim( + wallet: Wallet, + subtensor: "SubtensorInterface", + netuid: int, + max_n: int, + period: int, + prompt: bool, + json_output: bool, +) -> bool: + """ + Trims a subnet's UIDs to a specified amount + """ + print_verbose("Confirming subnet owner") + subnet_owner = await subtensor.query( + module="SubtensorModule", + storage_function="SubnetOwner", + params=[netuid], + ) + if subnet_owner != wallet.coldkeypub.ss58_address: + err_msg = "This wallet doesn't own the specified subnet." + if json_output: + json_console.print_json(data={"success": False, "message": err_msg}) + else: + err_console.print(f":cross_mark: [red]{err_msg}[/red]") + return False + if prompt and not json_output: + if not Confirm.ask( + f"You are about to trim UIDs on SN{netuid} to a limit of {max_n}", + default=False, + ): + err_console.print(":cross_mark: [red]User aborted.[/red]") + call = await subtensor.substrate.compose_call( + call_module="AdminUtils", + call_function="sudo_trim_to_max_allowed_uids", + call_params=[netuid, max_n], + ) + success, err_msg = await subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, era={"period": period} + ) + if not success: + if json_output: + json_console.print_json(data={"success": False, "message": err_msg}) + else: + err_console.print(f":cross_mark: [red]{err_msg}[/red]") + return False + else: + msg = f"Successfully trimmed UIDs on SN{netuid} to {max_n}" + if json_output: + json_console.print_json(data={"success": True, "message": msg}) + else: + console.print( + f":white_heavy_check_mark: [dark_sea_green3]{msg}[/dark_sea_green3]" + ) + return True From c4a182a8211c6002015e59f70d6eed1a37554319 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 17 Sep 2025 21:59:17 +0200 Subject: [PATCH 2/3] Updated and working --- bittensor_cli/cli.py | 4 ++-- bittensor_cli/src/commands/sudo.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d1cf71249..4484f5288 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -5378,8 +5378,8 @@ def sudo_trim( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], - validate=WV.WALLET_AND_HOTKEY, + ask_for=[WO.NAME, WO.PATH], + validate=WV.WALLET, ) self._run_command( sudo.trim( diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 64c9cd202..bb4bb43de 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -991,7 +991,7 @@ async def trim( call = await subtensor.substrate.compose_call( call_module="AdminUtils", call_function="sudo_trim_to_max_allowed_uids", - call_params=[netuid, max_n], + call_params={"netuid": netuid, "max_n": max_n}, ) success, err_msg = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, era={"period": period} From cb88ee67153c9229828c48b76fe6c8387562c951 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Wed, 17 Sep 2025 22:07:13 +0200 Subject: [PATCH 3/3] Added tests --- tests/e2e_tests/test_hyperparams_setting.py | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/e2e_tests/test_hyperparams_setting.py b/tests/e2e_tests/test_hyperparams_setting.py index 3af86c140..916b00cac 100644 --- a/tests/e2e_tests/test_hyperparams_setting.py +++ b/tests/e2e_tests/test_hyperparams_setting.py @@ -120,4 +120,53 @@ def test_hyperparams_setting(local_chain, wallet_setup): 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}") + # also test hidden hyperparam + 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", + "min_allowed_uids", + "--value", + "110", + ], + ) + cmd_json = json.loads(cmd.stdout) + assert cmd_json["success"] is True, (cmd.stdout, cmd_json) print("Successfully set hyperparameters") + print("Testing trimming UIDs") + cmd = exec_command_alice( + command="sudo", + sub_command="trim", + 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, + "--max", + "120", + "--json-out", + "--no-prompt", + ], + ) + cmd_json = json.loads(cmd.stdout) + assert cmd_json["success"] is True, (cmd.stdout, cmd_json) + print("Successfully trimmed UIDs")