From 3a6f45332308f47b30ad239f61b82ae9fdff5336 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 13 Jun 2025 12:46:19 -0700 Subject: [PATCH 1/5] add `SKIP_PULL` variable for conftest.py --- tests/e2e_tests/conftest.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index ba2c6c019..1b93ac0ae 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -107,7 +107,7 @@ def docker_runner(params): """Starts a Docker container before tests and gracefully terminates it after.""" def is_docker_running(): - """Check if Docker has been run.""" + """Check if Docker is running and optionally skip pulling the image.""" try: subprocess.run( ["docker", "info"], @@ -115,7 +115,13 @@ def is_docker_running(): stderr=subprocess.DEVNULL, check=True, ) - subprocess.run(["docker", "pull", LOCALNET_IMAGE_NAME], check=True) + + skip_pull = os.getenv("SKIP_PULL", "0") == "1" + if not skip_pull: + subprocess.run(["docker", "pull", LOCALNET_IMAGE_NAME], check=True) + else: + print(f"[SKIP_PULL=1] Skipping 'docker pull {LOCALNET_IMAGE_NAME}'") + return True except subprocess.CalledProcessError: return False From a815792900e8111797e145c9b21708d9e4f6824c Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 16 Jun 2025 13:07:01 -0700 Subject: [PATCH 2/5] adds netuid support in swap_hotkey --- bittensor_cli/cli.py | 8 ++- .../src/bittensor/extrinsics/registration.py | 69 +++++++++++++++---- bittensor_cli/src/commands/wallets.py | 2 + 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 60d8f4926..ecb936e7d 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1897,6 +1897,8 @@ def wallet_swap_hotkey( wallet_name: Optional[str] = Options.wallet_name, wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, + netuid: Optional[int] = Options.netuid_not_req, + all_netuids: bool = Options.all_netuids, network: Optional[list[str]] = Options.network, destination_hotkey_name: Optional[str] = typer.Argument( None, help="Destination hotkey name." @@ -1917,12 +1919,14 @@ def wallet_swap_hotkey( - Make sure that your original key pair (coldkeyA, hotkeyA) is already registered. - Make sure that you use a newly created hotkeyB in this command. A hotkeyB that is already registered cannot be used in this command. + - You can specify the netuid for which you want to swap the hotkey for. If it is not defined, the swap will be initiated for all subnets. - Finally, note that this command requires a fee of 1 TAO for recycling and this fee is taken from your wallet (coldkeyA). EXAMPLE - [green]$[/green] btcli wallet swap_hotkey destination_hotkey_name --wallet-name your_wallet_name --wallet-hotkey original_hotkey + [green]$[/green] btcli wallet swap_hotkey destination_hotkey_name --wallet-name your_wallet_name --wallet-hotkey original_hotkey --netuid 1 """ + netuid = get_optional_netuid(netuid, all_netuids) self.verbosity_handler(quiet, verbose, json_output) original_wallet = self.wallet_ask( wallet_name, @@ -1946,7 +1950,7 @@ def wallet_swap_hotkey( self.initialize_chain(network) return self._run_command( wallets.swap_hotkey( - original_wallet, new_wallet, self.subtensor, prompt, json_output + original_wallet, new_wallet, self.subtensor, netuid, prompt, json_output ) ) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index c230f1134..1e3328964 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -1611,7 +1611,8 @@ def _update_curr_block( """ Update the current block data with the provided block information and difficulty. - This function updates the current block and its difficulty in a thread-safe manner. It sets the current block + This function updates the current block + and its difficulty in a thread-safe manner. It sets the current block number, hashes the block with the hotkey, updates the current block bytes, and packs the difficulty. :param curr_diff: Shared array to store the current difficulty. @@ -1745,6 +1746,7 @@ async def swap_hotkey_extrinsic( subtensor: "SubtensorInterface", wallet: Wallet, new_wallet: Wallet, + netuid: Optional[int] = None, prompt: bool = False, ) -> bool: """ @@ -1756,37 +1758,76 @@ async def swap_hotkey_extrinsic( netuids_registered = await subtensor.get_netuids_for_hotkey( wallet.hotkey.ss58_address, block_hash=block_hash ) - if not len(netuids_registered) > 0: + netuids_registered_new_hotkey = await subtensor.get_netuids_for_hotkey( + new_wallet.hotkey.ss58_address, block_hash=block_hash + ) + + if netuid is not None and netuid not in netuids_registered: + err_console.print( + f":cross_mark: [red]Failed[/red]: Original hotkey {wallet.hotkey.ss58_address} is not registered on subnet {netuid}" + ) + return False + + elif not len(netuids_registered) > 0: err_console.print( - f"Destination hotkey [dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange] is not registered. " + f"Original hotkey [dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] is not registered on any subnet. " f"Please register and try again" ) return False + if netuid is not None: + if netuid in netuids_registered_new_hotkey: + err_console.print( + f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} " + f"is already registered on subnet {netuid}" + ) + return False + else: + if len(netuids_registered_new_hotkey) > 0: + err_console.print( + f":cross_mark: [red]Failed[/red]: New hotkey {new_wallet.hotkey.ss58_address} " + f"is already registered on subnet(s) {netuids_registered_new_hotkey}" + ) + return False + if not unlock_key(wallet).success: return False if prompt: # Prompt user for confirmation. - if not Confirm.ask( - f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t" - f"[dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] with hotkey \n\t" - f"[dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange]\n" - "This operation will cost [bold cyan]1 TAO t (recycled)[/bold cyan]" - ): + if netuid is not None: + confirm_message = ( + f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t" + f"[dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] with hotkey \n\t" + f"[dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange] on subnet {netuid}\n" + "This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]" + ) + else: + confirm_message = ( + f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t" + f"[dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] with hotkey \n\t" + f"[dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange] on all subnets\n" + "This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]" + ) + + if not Confirm.ask(confirm_message): return False print_verbose( f"Swapping {wallet.name}'s hotkey ({wallet.hotkey.ss58_address}) with " - f"{new_wallet.name}s hotkey ({new_wallet.hotkey.ss58_address})" + f"{new_wallet.name}'s hotkey ({new_wallet.hotkey.ss58_address})" ) with console.status(":satellite: Swapping hotkeys...", spinner="aesthetic"): + call_params = { + "hotkey": wallet.hotkey.ss58_address, + "new_hotkey": new_wallet.hotkey.ss58_address, + } + if netuid is not None: + call_params["netuid"] = netuid + call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="swap_hotkey", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "new_hotkey": new_wallet.hotkey.ss58_address, - }, + call_params=call_params, ) success, err_msg = await subtensor.sign_and_send_extrinsic(call, wallet) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index 40ea61218..c1c4f4261 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1632,6 +1632,7 @@ async def swap_hotkey( original_wallet: Wallet, new_wallet: Wallet, subtensor: SubtensorInterface, + netuid: Optional[int], prompt: bool, json_output: bool, ): @@ -1640,6 +1641,7 @@ async def swap_hotkey( subtensor, original_wallet, new_wallet, + netuid=netuid, prompt=prompt, ) if json_output: From 668c8348961dbed15d7eb8d685d5251596c70918 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 16 Jun 2025 14:35:16 -0700 Subject: [PATCH 3/5] bumps version and changelog --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6413ce04f..4f646dc19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 9.7.0/2025-06-16 + +## What's Changed +* Add `SKIP_PULL` variable for conftest.py by @basfroman in https://github.com/opentensor/btcli/pull/502 +* Feat: Adds netuid support in swap_hotkeys by @ibraheem-abe in https://github.com/opentensor/btcli/pull/505 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v9.6.0...v9.7.0 + ## 9.6.0/2025-06-12 ## What's Changed diff --git a/pyproject.toml b/pyproject.toml index e680c1e57..fdafdd92f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor-cli" -version = "9.6.0" +version = "9.7.0" description = "Bittensor CLI" readme = "README.md" authors = [ From faf6217b1cfd4bc8691cd51d3123ff2773e661d6 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 16 Jun 2025 14:45:58 -0700 Subject: [PATCH 4/5] update netuid in call_params --- bittensor_cli/src/bittensor/extrinsics/registration.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index 1e3328964..527fedcc7 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -1820,9 +1820,8 @@ async def swap_hotkey_extrinsic( call_params = { "hotkey": wallet.hotkey.ss58_address, "new_hotkey": new_wallet.hotkey.ss58_address, + "netuid": netuid, } - if netuid is not None: - call_params["netuid"] = netuid call = await subtensor.substrate.compose_call( call_module="SubtensorModule", From 5f401aa157bd616f754416a784fa2b6a093b2227 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Mon, 16 Jun 2025 14:55:08 -0700 Subject: [PATCH 5/5] improve wallet info in swaps --- .../src/bittensor/extrinsics/registration.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/registration.py b/bittensor_cli/src/bittensor/extrinsics/registration.py index 527fedcc7..8bbc8064f 100644 --- a/bittensor_cli/src/bittensor/extrinsics/registration.py +++ b/bittensor_cli/src/bittensor/extrinsics/registration.py @@ -1798,23 +1798,23 @@ async def swap_hotkey_extrinsic( if netuid is not None: confirm_message = ( f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t" - f"[dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] with hotkey \n\t" - f"[dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange] on subnet {netuid}\n" + f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t" + f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on subnet {netuid}\n" "This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]" ) else: confirm_message = ( f"Do you want to swap [dark_orange]{wallet.name}[/dark_orange] hotkey \n\t" - f"[dark_orange]{wallet.hotkey.ss58_address}[/dark_orange] with hotkey \n\t" - f"[dark_orange]{new_wallet.hotkey.ss58_address}[/dark_orange] on all subnets\n" + f"[dark_orange]{wallet.hotkey.ss58_address} ({wallet.hotkey_str})[/dark_orange] with hotkey \n\t" + f"[dark_orange]{new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})[/dark_orange] on all subnets\n" "This operation will cost [bold cyan]1 TAO (recycled)[/bold cyan]" ) if not Confirm.ask(confirm_message): return False print_verbose( - f"Swapping {wallet.name}'s hotkey ({wallet.hotkey.ss58_address}) with " - f"{new_wallet.name}'s hotkey ({new_wallet.hotkey.ss58_address})" + f"Swapping {wallet.name}'s hotkey ({wallet.hotkey.ss58_address} - {wallet.hotkey_str}) with " + f"{new_wallet.name}'s hotkey ({new_wallet.hotkey.ss58_address} - {new_wallet.hotkey_str})" ) with console.status(":satellite: Swapping hotkeys...", spinner="aesthetic"): call_params = { @@ -1832,7 +1832,7 @@ async def swap_hotkey_extrinsic( if success: console.print( - f"Hotkey {wallet.hotkey} swapped for new hotkey: {new_wallet.hotkey}" + f"Hotkey {wallet.hotkey.ss58_address} ({wallet.hotkey_str}) swapped for new hotkey: {new_wallet.hotkey.ss58_address} ({new_wallet.hotkey_str})" ) return True else: