From 9c68196bf95cc921883a8b1c4e4fbf15603c8480 Mon Sep 17 00:00:00 2001 From: Dmitry <98899785+mdqst@users.noreply.github.com> Date: Thu, 10 Apr 2025 21:33:26 +0300 Subject: [PATCH 01/15] docs: fixed redundant "from" --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d33a2760..4e9a5d195 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Installation steps are described below. For a full documentation on how to use ` ## Install on macOS and Linux -You can install `btcli` on your local machine directly from source, or from from PyPI. **Make sure you verify your installation after you install**: +You can install `btcli` on your local machine directly from source, or from PyPI. **Make sure you verify your installation after you install**: ### Install from PyPI From b8cadfc7f6cf85a68d41c644905a13202c6833ad Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Apr 2025 22:17:47 +0200 Subject: [PATCH 02/15] Allows for passing stake sudo test because max_burn is now root sudo only --- bittensor_cli/cli.py | 17 +++++++++++++++++ bittensor_cli/src/__init__.py | 2 +- bittensor_cli/src/commands/sudo.py | 6 ++++-- tests/e2e_tests/test_staking_sudo.py | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 9df39d24e..80ddc4a0d 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -4430,6 +4430,7 @@ def sudo_set( param_value: Optional[str] = typer.Option( "", "--value", help="Value to set the hyperparameter to." ), + prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, json_output: bool = Options.json_output, @@ -4454,6 +4455,11 @@ def sudo_set( raise typer.Exit() if not param_name: + if not prompt: + err_console.print( + "Param name not supplied with `--no-prompt` flag. Cannot continue" + ) + return False hyperparam_list = [field.name for field in fields(SubnetHyperparameters)] console.print("Available hyperparameters:\n") for idx, param in enumerate(hyperparam_list, start=1): @@ -4467,6 +4473,11 @@ def sudo_set( param_name = hyperparam_list[choice - 1] if param_name in ["alpha_high", "alpha_low"]: + if not prompt: + err_console.print( + "`alpha_high` and `alpha_low` values cannot be set with `--no-prompt`" + ) + return False param_name = "alpha_values" low_val = FloatPrompt.ask( "Enter the new value for [dark_orange]alpha_low[/dark_orange]" @@ -4477,6 +4488,11 @@ def sudo_set( param_value = f"{low_val},{high_val}" if not param_value: + if not prompt: + err_console.print( + "Param value not supplied with `--no-prompt` flag. Cannot continue." + ) + return False if HYPERPARAMS.get(param_name): param_value = Prompt.ask( f"Enter the new value for [{COLORS.G.SUBHEAD}]{param_name}[/{COLORS.G.SUBHEAD}] " @@ -4495,6 +4511,7 @@ def sudo_set( netuid, param_name, param_value, + prompt, json_output, ) ) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index 5c508812c..d1188e547 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -640,7 +640,7 @@ class WalletValidationTypes(Enum): "activity_cutoff": ("sudo_set_activity_cutoff", False), "target_regs_per_interval": ("sudo_set_target_registrations_per_interval", True), "min_burn": ("sudo_set_min_burn", True), - "max_burn": ("sudo_set_max_burn", False), + "max_burn": ("sudo_set_max_burn", True), "bonds_moving_avg": ("sudo_set_bonds_moving_average", False), "max_regs_per_block": ("sudo_set_max_registrations_per_block", True), "serving_rate_limit": ("sudo_set_serving_rate_limit", False), diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index e5502714a..87a54d520 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -145,6 +145,7 @@ async def set_hyperparameter_extrinsic( value: Optional[Union[str, bool, float, list[float]]], wait_for_inclusion: bool = False, wait_for_finalization: bool = True, + prompt: bool = True, ) -> bool: """Sets a hyperparameter for a specific subnetwork. @@ -190,7 +191,7 @@ async def set_hyperparameter_extrinsic( ":cross_mark: [red]Invalid hyperparameter specified.[/red]" ) return False - if sudo_: + 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" ): @@ -574,6 +575,7 @@ async def sudo_set_hyperparameter( netuid: int, param_name: str, param_value: Optional[str], + prompt: bool, json_output: bool, ): """Set subnet hyperparameters.""" @@ -602,7 +604,7 @@ async def sudo_set_hyperparameter( ) return False success = await set_hyperparameter_extrinsic( - subtensor, wallet, netuid, param_name, value + subtensor, wallet, netuid, param_name, value, prompt=prompt ) if json_output: return success diff --git a/tests/e2e_tests/test_staking_sudo.py b/tests/e2e_tests/test_staking_sudo.py index 0877f64be..bd7350616 100644 --- a/tests/e2e_tests/test_staking_sudo.py +++ b/tests/e2e_tests/test_staking_sudo.py @@ -339,6 +339,7 @@ def test_staking(local_chain, wallet_setup): "max_burn", "--value", "10000000000", # In RAO, TAO = 10 + "--no-prompt", ], ) assert ( From 9e1247235c87ccb0888e61b4b8810803fd9b6226 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Apr 2025 16:18:04 +0200 Subject: [PATCH 03/15] Fixes cases where err_docs is a string --- bittensor_cli/src/bittensor/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 992c2a2e3..837261f3b 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -558,7 +558,11 @@ 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 = err_docs[0] if err_docs else err_description + if err_docs: + if isinstance(err_docs, list): + err_description = err_docs[0] + else: + err_description = err_docs return f"Subtensor returned `{err_name}({err_type})` error. This means: `{err_description}`." From 78a45674cf67a0b86f9fb3aba549f70f12169f91 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Apr 2025 23:14:04 +0200 Subject: [PATCH 04/15] Account for multiple doc lines in error --- bittensor_cli/src/bittensor/utils.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 837261f3b..bde5930f7 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -558,11 +558,10 @@ 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]) - if err_docs: - if isinstance(err_docs, list): - err_description = err_docs[0] - else: - err_description = err_docs + if isinstance(err_docs, list): + err_description = "; ".join(err_docs) + else: + err_description = err_docs return f"Subtensor returned `{err_name}({err_type})` error. This means: `{err_description}`." From 26772a951ea065ab6ef4acc4981c08f75e977528 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 29 Apr 2025 17:40:42 +0200 Subject: [PATCH 05/15] Pulls shares in a gather rather than one-at-a-time. +ruff --- bittensor_cli/src/__init__.py | 2 +- .../src/bittensor/subtensor_interface.py | 38 +++++++++---------- bittensor_cli/src/commands/subnets/subnets.py | 4 +- tests/e2e_tests/test_unstaking.py | 23 +++++++---- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index d1188e547..bb3bd07f4 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -38,7 +38,7 @@ class Constants: "test": "0x8f9cf856bf558a14440e75569c9e58594757048d7b3a84b5d25f6bd978263105", } delegates_detail_url = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json" - emission_start_schedule = 7 * 24 * 60 * 60 / 12 # 7 days + emission_start_schedule = 7 * 24 * 60 * 60 / 12 # 7 days @dataclass diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 7f21f90d4..2bd0057bd 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -238,25 +238,25 @@ async def get_stake_for_coldkey_and_hotkey( :return: Balance: The stake under the coldkey - hotkey pairing. """ - alpha_shares = await self.query( - module="SubtensorModule", - storage_function="Alpha", - params=[hotkey_ss58, coldkey_ss58, netuid], - block_hash=block_hash, - ) - - hotkey_alpha = await self.query( - module="SubtensorModule", - storage_function="TotalHotkeyAlpha", - params=[hotkey_ss58, netuid], - block_hash=block_hash, - ) - - hotkey_shares = await self.query( - module="SubtensorModule", - storage_function="TotalHotkeyShares", - params=[hotkey_ss58, netuid], - block_hash=block_hash, + alpha_shares, hotkey_alpha, hotkey_shares = await asyncio.gather( + self.query( + module="SubtensorModule", + storage_function="Alpha", + params=[hotkey_ss58, coldkey_ss58, netuid], + block_hash=block_hash, + ), + self.query( + module="SubtensorModule", + storage_function="TotalHotkeyAlpha", + params=[hotkey_ss58, netuid], + block_hash=block_hash, + ), + self.query( + module="SubtensorModule", + storage_function="TotalHotkeyShares", + params=[hotkey_ss58, netuid], + block_hash=block_hash, + ), ) alpha_shares_as_float = fixed_to_float(alpha_shares or 0) diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index f5eb939c3..81febc4b4 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -2412,7 +2412,9 @@ async def start_subnet( else: error_msg = format_error_message(await response.error_message) if "FirstEmissionBlockNumberAlreadySet" in error_msg: - console.print(f"[dark_sea_green3]Subnet {netuid} already has an emission schedule.[/dark_sea_green3]") + console.print( + f"[dark_sea_green3]Subnet {netuid} already has an emission schedule.[/dark_sea_green3]" + ) return True await get_start_schedule(subtensor, netuid) diff --git a/tests/e2e_tests/test_unstaking.py b/tests/e2e_tests/test_unstaking.py index 02b25489c..4db52d2b5 100644 --- a/tests/e2e_tests/test_unstaking.py +++ b/tests/e2e_tests/test_unstaking.py @@ -6,6 +6,7 @@ from btcli.tests.e2e_tests.utils import set_storage_extrinsic + def test_unstaking(local_chain, wallet_setup): """ Test various unstaking scenarios including partial unstake, unstake all alpha, and unstake all. @@ -38,16 +39,22 @@ def test_unstaking(local_chain, wallet_setup): # Call to make Alice root owner items = [ - ( - bytes.fromhex("658faa385070e074c85bf6b568cf055536e3e82152c8758267395fe524fbbd160000"), - bytes.fromhex("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") + ( + bytes.fromhex( + "658faa385070e074c85bf6b568cf055536e3e82152c8758267395fe524fbbd160000" + ), + bytes.fromhex( + "d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" + ), ) ] - asyncio.run(set_storage_extrinsic( - local_chain, - wallet=wallet_alice, - items=items, - )) + asyncio.run( + set_storage_extrinsic( + local_chain, + wallet=wallet_alice, + items=items, + ) + ) # Create first subnet (netuid = 2) result = exec_command_alice( From 2a962e0facd6d0aea7266534e8aebea161dbe13e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Apr 2025 23:25:56 +0200 Subject: [PATCH 06/15] ship --- bittensor_cli/src/bittensor/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index bde5930f7..ca95b1b45 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -559,7 +559,7 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: err_name = error_message.get("name", err_name) err_docs = error_message.get("docs", [err_description]) if isinstance(err_docs, list): - err_description = "; ".join(err_docs) + err_description = " ".join(err_docs) else: err_description = err_docs From 0b917da96f3e83c9c7622cb25309af225883702a Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 2 May 2025 13:56:09 +0200 Subject: [PATCH 07/15] Pull emission_start_schedule dynamically rather than a predefined constant. --- bittensor_cli/src/__init__.py | 1 - bittensor_cli/src/commands/subnets/subnets.py | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index bb3bd07f4..ce55bfcd6 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -38,7 +38,6 @@ class Constants: "test": "0x8f9cf856bf558a14440e75569c9e58594757048d7b3a84b5d25f6bd978263105", } delegates_detail_url = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json" - emission_start_schedule = 7 * 24 * 60 * 60 / 12 # 7 days @dataclass diff --git a/bittensor_cli/src/commands/subnets/subnets.py b/bittensor_cli/src/commands/subnets/subnets.py index 81febc4b4..fd050bd0a 100644 --- a/bittensor_cli/src/commands/subnets/subnets.py +++ b/bittensor_cli/src/commands/subnets/subnets.py @@ -2320,14 +2320,21 @@ async def get_start_schedule( if not await subtensor.subnet_exists(netuid): print_error(f"Subnet {netuid} does not exist.") return None - - registration_block = await subtensor.query( - module="SubtensorModule", - storage_function="NetworkRegisteredAt", - params=[netuid], + block_hash = await subtensor.substrate.get_chain_head() + registration_block, min_blocks_to_start, current_block = await asyncio.gather( + subtensor.query( + module="SubtensorModule", + storage_function="NetworkRegisteredAt", + params=[netuid], + block_hash=block_hash, + ), + subtensor.substrate.get_constant( + module_name="SubtensorModule", + constant_name="DurationOfStartCall", + block_hash=block_hash, + ), + subtensor.substrate.get_block_number(block_hash=block_hash), ) - min_blocks_to_start = Constants.emission_start_schedule - current_block = await subtensor.substrate.get_block_number() potential_start_block = registration_block + min_blocks_to_start if current_block < potential_start_block: From 51da68e32df5941f82117fae5f676af1718db294 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 5 May 2025 20:00:58 +0200 Subject: [PATCH 08/15] Change default era period to 10. --- 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 80ddc4a0d..75ad791d4 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -294,7 +294,7 @@ class Options: help="Outputs the result of the command as JSON.", ) era: int = typer.Option( - 3, help="Length (in blocks) for which the transaction should be valid." + 10, help="Length (in blocks) for which the transaction should be valid." ) From a1501d7b3b8e4b8e485e4e92464275ef1a8cc755 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 5 May 2025 20:53:11 +0200 Subject: [PATCH 09/15] Update "era" to "period" --- bittensor_cli/cli.py | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 75ad791d4..d03f41dab 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -293,8 +293,11 @@ class Options: "--json-out", help="Outputs the result of the command as JSON.", ) - era: int = typer.Option( - 10, help="Length (in blocks) for which the transaction should be valid." + period: int = typer.Option( + 10, + "--period", + "--era", + help="Length (in blocks) for which the transaction should be valid.", ) @@ -1792,7 +1795,7 @@ def wallet_transfer( transfer_all: bool = typer.Option( False, "--all", prompt=False, help="Transfer all available balance." ), - era: int = Options.era, + period: int = Options.period, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -1847,7 +1850,7 @@ def wallet_transfer( destination=destination_ss58_address, amount=amount, transfer_all=transfer_all, - era=era, + era=period, prompt=prompt, json_output=json_output, ) @@ -3190,7 +3193,7 @@ def stake_add( rate_tolerance: Optional[float] = Options.rate_tolerance, safe_staking: Optional[bool] = Options.safe_staking, allow_partial_stake: Optional[bool] = Options.allow_partial_stake, - era: int = Options.era, + period: int = Options.period, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3386,7 +3389,7 @@ def stake_add( rate_tolerance, allow_partial_stake, json_output, - era, + period, ) ) @@ -3438,7 +3441,7 @@ def stake_remove( rate_tolerance: Optional[float] = Options.rate_tolerance, safe_staking: Optional[bool] = Options.safe_staking, allow_partial_stake: Optional[bool] = Options.allow_partial_stake, - era: int = Options.era, + period: int = Options.period, prompt: bool = Options.prompt, interactive: bool = typer.Option( False, @@ -3631,7 +3634,7 @@ def stake_remove( exclude_hotkeys=exclude_hotkeys, prompt=prompt, json_output=json_output, - era=era, + era=period, ) ) elif ( @@ -3687,7 +3690,7 @@ def stake_remove( rate_tolerance=rate_tolerance, allow_partial_stake=allow_partial_stake, json_output=json_output, - era=era, + era=period, ) ) @@ -3715,7 +3718,7 @@ def stake_move( stake_all: bool = typer.Option( False, "--stake-all", "--all", help="Stake all", prompt=False ), - era: int = Options.era, + period: int = Options.period, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3845,7 +3848,7 @@ def stake_move( destination_hotkey=destination_hotkey, amount=amount, stake_all=stake_all, - era=era, + era=period, interactive_selection=interactive_selection, prompt=prompt, ) @@ -3886,7 +3889,7 @@ def stake_transfer( stake_all: bool = typer.Option( False, "--stake-all", "--all", help="Stake all", prompt=False ), - era: int = Options.era, + period: int = Options.period, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -4008,7 +4011,7 @@ def stake_transfer( dest_netuid=dest_netuid, dest_coldkey_ss58=dest_ss58, amount=amount, - era=era, + era=period, interactive_selection=interactive_selection, stake_all=stake_all, prompt=prompt, @@ -4050,7 +4053,7 @@ def stake_swap( "--all", help="Swap all available stake", ), - era: int = Options.era, + period: int = Options.period, prompt: bool = Options.prompt, wait_for_inclusion: bool = Options.wait_for_inclusion, wait_for_finalization: bool = Options.wait_for_finalization, @@ -4115,7 +4118,7 @@ def stake_swap( destination_netuid=dest_netuid, amount=amount, swap_all=swap_all, - era=era, + era=period, interactive_selection=interactive_selection, prompt=prompt, wait_for_inclusion=wait_for_inclusion, @@ -5275,10 +5278,12 @@ def subnets_register( wallet_hotkey: str = Options.wallet_hotkey, network: Optional[list[str]] = Options.network, netuid: int = Options.netuid, - era: Optional[ + period: Optional[ int - ] = typer.Option( # Should not be Options.era bc this needs to be an Optional[int] + ] = typer.Option( # Should not be Options.period bc this needs to be an Optional[int] None, + "--period", + "--era", help="Length (in blocks) for which the transaction should be valid. Note that it is possible that if you " "use an era for this transaction that you may pay a different fee to register than the one stated.", ), @@ -5311,7 +5316,7 @@ def subnets_register( wallet, self.initialize_chain(network), netuid, - era, + period, json_output, prompt, ) From deff48fe25d8f6e78ee933045c949a39e348828b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 5 May 2025 21:19:52 +0200 Subject: [PATCH 10/15] Update default era period --- 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 d03f41dab..b2f933417 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -294,7 +294,7 @@ class Options: help="Outputs the result of the command as JSON.", ) period: int = typer.Option( - 10, + 16, "--period", "--era", help="Length (in blocks) for which the transaction should be valid.", From 90c1796485a49a9cdbf9dfb1c538955a25570ada Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 12 May 2025 16:16:13 +0200 Subject: [PATCH 11/15] Fixes name-shadowing bug. --- bittensor_cli/cli.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 80ddc4a0d..e2c55a41d 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -436,14 +436,14 @@ def parse_mnemonic(mnemonic: str) -> str: def get_creation_data( mnemonic: Optional[str], seed: Optional[str], - json: Optional[str], + json_path: Optional[str], json_password: Optional[str], ) -> tuple[str, str, str, str]: """ Determines which of the key creation elements have been supplied, if any. If None have been supplied, prompts to user, and determines what they've supplied. Returns all elements in a tuple. """ - if not mnemonic and not seed and not json: + if not mnemonic and not seed and not json_path: prompt_answer = Prompt.ask( "Enter the mnemonic, or the seed hex string, or the location of the JSON file." ) @@ -452,20 +452,20 @@ def get_creation_data( elif len(prompt_answer.split(" ")) > 1: mnemonic = parse_mnemonic(prompt_answer) else: - json = prompt_answer + json_path = prompt_answer elif mnemonic: mnemonic = parse_mnemonic(mnemonic) - if json: - if not os.path.exists(json): - print_error(f"The JSON file '{json}' does not exist.") + if json_path: + if not os.path.exists(json_path): + print_error(f"The JSON file '{json_path}' does not exist.") raise typer.Exit() - if json and not json_password: + if json_path and not json_password: json_password = Prompt.ask( "Enter the backup password for JSON file.", password=True ) - return mnemonic, seed, json, json_password + return mnemonic, seed, json_path, json_password def config_selector(conf: dict, title: str): @@ -2090,7 +2090,7 @@ def wallet_regen_coldkey( wallet_hotkey: Optional[str] = Options.wallet_hotkey, mnemonic: Optional[str] = Options.mnemonic, seed: Optional[str] = Options.seed, - json: Optional[str] = Options.json, + json_path: Optional[str] = Options.json, json_password: Optional[str] = Options.json_password, use_password: Optional[bool] = Options.use_password, overwrite: bool = Options.overwrite, @@ -2130,15 +2130,15 @@ def wallet_regen_coldkey( wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) - mnemonic, seed, json, json_password = get_creation_data( - mnemonic, seed, json, json_password + mnemonic, seed, json_path, json_password = get_creation_data( + mnemonic, seed, json_path, json_password ) return self._run_command( wallets.regen_coldkey( wallet, mnemonic, seed, - json, + json_path, json_password, use_password, overwrite, @@ -2214,7 +2214,7 @@ def wallet_regen_hotkey( wallet_hotkey: Optional[str] = Options.wallet_hotkey, mnemonic: Optional[str] = Options.mnemonic, seed: Optional[str] = Options.seed, - json: Optional[str] = Options.json, + json_path: Optional[str] = Options.json, json_password: Optional[str] = Options.json_password, use_password: bool = typer.Option( False, # Overriden to False @@ -2250,15 +2250,15 @@ def wallet_regen_hotkey( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET, ) - mnemonic, seed, json, json_password = get_creation_data( - mnemonic, seed, json, json_password + mnemonic, seed, json_path, json_password = get_creation_data( + mnemonic, seed, json_path, json_password ) return self._run_command( wallets.regen_hotkey( wallet, mnemonic, seed, - json, + json_path, json_password, use_password, overwrite, From 56f193294d233f512bbced52cf444e91be76af5e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 12 May 2025 16:48:05 +0200 Subject: [PATCH 12/15] Adds explicit choosing rather than parsing so as to ensure we do not incorrectly assume the entry the user wishes to make. Also correctly snips the leading '0x' from seed hex. --- bittensor_cli/cli.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index e2c55a41d..8a6ddac3a 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -444,14 +444,27 @@ def get_creation_data( prompts to user, and determines what they've supplied. Returns all elements in a tuple. """ if not mnemonic and not seed and not json_path: - prompt_answer = Prompt.ask( - "Enter the mnemonic, or the seed hex string, or the location of the JSON file." + choices = { + 1: "mnemonic", + 2: "seed hex string", + 3: "path to JSON File", + } + type_answer = IntPrompt.ask( + "Select one of the following to enter\n" + f"[{COLORS.G.HINT}][1][/{COLORS.G.HINT}] Mnemonic\n" + f"[{COLORS.G.HINT}][2][/{COLORS.G.HINT}] Seed hex string\n" + f"[{COLORS.G.HINT}][3][/{COLORS.G.HINT}] Path to JSON File\n", + choices=["1", "2", "3"], + show_choices=False, ) - if prompt_answer.startswith("0x"): + prompt_answer = Prompt.ask(f"Please enter your {choices[type_answer]}") + if type_answer == 1: + mnemonic = prompt_answer + elif type_answer == 2: seed = prompt_answer - elif len(prompt_answer.split(" ")) > 1: - mnemonic = parse_mnemonic(prompt_answer) - else: + if seed.startswith("0x"): + seed = seed[2:] + elif type_answer == 3: json_path = prompt_answer elif mnemonic: mnemonic = parse_mnemonic(mnemonic) From b8e7ac7fc7eae3181023043763657d736e72855e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 12 May 2025 17:18:23 +0200 Subject: [PATCH 13/15] Fixes broken click dependency (sub-dependency of typer) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c9843b421..4e1d4d5c5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "async-substrate-interface>=1.1.0", "aiohttp~=3.10.2", "backoff~=2.2.1", + "click<8.2.0", # typer.testing.CliRunner(mix_stderr=) is broken in click 8.2.0+ "GitPython>=3.0.0", "fuzzywuzzy~=0.18.0", "netaddr~=1.3.0", From f617a19f9cdbd73cc42af68b16924e9da4a9a8d6 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 12 May 2025 16:16:13 +0200 Subject: [PATCH 14/15] Fixes name-shadowing bug. --- bittensor_cli/cli.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index b2f933417..516c458e1 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -439,14 +439,14 @@ def parse_mnemonic(mnemonic: str) -> str: def get_creation_data( mnemonic: Optional[str], seed: Optional[str], - json: Optional[str], + json_path: Optional[str], json_password: Optional[str], ) -> tuple[str, str, str, str]: """ Determines which of the key creation elements have been supplied, if any. If None have been supplied, prompts to user, and determines what they've supplied. Returns all elements in a tuple. """ - if not mnemonic and not seed and not json: + if not mnemonic and not seed and not json_path: prompt_answer = Prompt.ask( "Enter the mnemonic, or the seed hex string, or the location of the JSON file." ) @@ -455,20 +455,20 @@ def get_creation_data( elif len(prompt_answer.split(" ")) > 1: mnemonic = parse_mnemonic(prompt_answer) else: - json = prompt_answer + json_path = prompt_answer elif mnemonic: mnemonic = parse_mnemonic(mnemonic) - if json: - if not os.path.exists(json): - print_error(f"The JSON file '{json}' does not exist.") + if json_path: + if not os.path.exists(json_path): + print_error(f"The JSON file '{json_path}' does not exist.") raise typer.Exit() - if json and not json_password: + if json_path and not json_password: json_password = Prompt.ask( "Enter the backup password for JSON file.", password=True ) - return mnemonic, seed, json, json_password + return mnemonic, seed, json_path, json_password def config_selector(conf: dict, title: str): @@ -2093,7 +2093,7 @@ def wallet_regen_coldkey( wallet_hotkey: Optional[str] = Options.wallet_hotkey, mnemonic: Optional[str] = Options.mnemonic, seed: Optional[str] = Options.seed, - json: Optional[str] = Options.json, + json_path: Optional[str] = Options.json, json_password: Optional[str] = Options.json_password, use_password: Optional[bool] = Options.use_password, overwrite: bool = Options.overwrite, @@ -2133,15 +2133,15 @@ def wallet_regen_coldkey( wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) - mnemonic, seed, json, json_password = get_creation_data( - mnemonic, seed, json, json_password + mnemonic, seed, json_path, json_password = get_creation_data( + mnemonic, seed, json_path, json_password ) return self._run_command( wallets.regen_coldkey( wallet, mnemonic, seed, - json, + json_path, json_password, use_password, overwrite, @@ -2217,7 +2217,7 @@ def wallet_regen_hotkey( wallet_hotkey: Optional[str] = Options.wallet_hotkey, mnemonic: Optional[str] = Options.mnemonic, seed: Optional[str] = Options.seed, - json: Optional[str] = Options.json, + json_path: Optional[str] = Options.json, json_password: Optional[str] = Options.json_password, use_password: bool = typer.Option( False, # Overriden to False @@ -2253,15 +2253,15 @@ def wallet_regen_hotkey( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET, ) - mnemonic, seed, json, json_password = get_creation_data( - mnemonic, seed, json, json_password + mnemonic, seed, json_path, json_password = get_creation_data( + mnemonic, seed, json_path, json_password ) return self._run_command( wallets.regen_hotkey( wallet, mnemonic, seed, - json, + json_path, json_password, use_password, overwrite, From 81a2da5a40e447bffce84e3fa598c0406e5abfbe Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 12 May 2025 14:38:18 -0700 Subject: [PATCH 15/15] bumps version and changelog --- CHANGELOG.md | 15 +++++++++++++++ pyproject.toml | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4bbc0128..be2bc9ade 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 9.4.1 /2025-04-17 + +## What's Changed +* Fixes `test_staking_sudo` setting `max_burn` by @thewhaleking in https://github.com/opentensor/btcli/pull/440 +* Fixes Error Formatter by @thewhaleking in https://github.com/opentensor/btcli/pull/439 +* Pulls shares in a gather rather than one-at-a-time by @thewhaleking in https://github.com/opentensor/btcli/pull/438 +* Pull emission start schedule dynamically by @thewhaleking in https://github.com/opentensor/btcli/pull/442 +* Lengthen default era period + rename "era" to "period" by @thewhaleking in https://github.com/opentensor/btcli/pull/443 +* docs: fixed redundant "from" by @mdqst in https://github.com/opentensor/btcli/pull/429 +* click version 8.2.0 broken by @thewhaleking in https://github.com/opentensor/btcli/pull/447 +* JSON Name shadowing bug by @thewhaleking in https://github.com/opentensor/btcli/pull/445 +* Stop Parsing, Start Asking by @thewhaleking in https://github.com/opentensor/btcli/pull/446 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.4.0...v9.4.1 + ## 9.4.0 /2025-04-17 ## What's Changed diff --git a/pyproject.toml b/pyproject.toml index 4e1d4d5c5..240c6f892 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor-cli" -version = "9.4.0" +version = "9.4.1" description = "Bittensor CLI" readme = "README.md" authors = [