From 7ab06c97a85ec8d01e4cd44f3454dd42dc27e7cf Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 29 Oct 2024 22:30:21 +0200 Subject: [PATCH 01/11] Use correct dict key --- bittensor_cli/src/bittensor/extrinsics/transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/src/bittensor/extrinsics/transfer.py b/bittensor_cli/src/bittensor/extrinsics/transfer.py index d5311c068..ab2056802 100644 --- a/bittensor_cli/src/bittensor/extrinsics/transfer.py +++ b/bittensor_cli/src/bittensor/extrinsics/transfer.py @@ -193,7 +193,7 @@ async def do_transfer() -> tuple[bool, str, str]: ) console.print( f"Balance:\n" - f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkey.ss58_address]}[/green]" + f" [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]" ) return True From 6f63cdea62572afbb64955e6f0fac449e5afd7fe Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 15:37:47 +0200 Subject: [PATCH 02/11] Clean up error handling for main CLI. Add KeyboardInterrupt. --- bittensor_cli/cli.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index b3cc16ff7..d7c6180a8 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -822,24 +822,23 @@ def _run_command(self, cmd: Coroutine) -> None: """ async def _run(): + run_cmd = asyncio.create_task(cmd) try: if self.subtensor: async with self.subtensor: - result = await cmd + result = await run_cmd else: - result = await cmd + result = await run_cmd return result except (ConnectionRefusedError, ssl.SSLError): err_console.print(f"Unable to connect to the chain: {self.subtensor}") - asyncio.create_task(cmd).cancel() raise typer.Exit() - except ConnectionClosed: - asyncio.create_task(cmd).cancel() - raise typer.Exit() - except SubstrateRequestException as e: - err_console.print(str(e)) - asyncio.create_task(cmd).cancel() + except (ConnectionClosed, SubstrateRequestException, KeyboardInterrupt) as e: + if isinstance(e, SubstrateRequestException): + err_console.print(str(e)) raise typer.Exit() + finally: + run_cmd.cancel() if sys.version_info < (3, 10): # For Python 3.9 or lower From 01235ab1f8582476531ff44464d7f5f6bbfca576 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 17:17:22 +0200 Subject: [PATCH 03/11] Race conditions for metadata set. --- bittensor_cli/cli.py | 6 +++- .../bittensor/async_substrate_interface.py | 34 +++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d7c6180a8..a95bef267 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -833,7 +833,11 @@ async def _run(): except (ConnectionRefusedError, ssl.SSLError): err_console.print(f"Unable to connect to the chain: {self.subtensor}") raise typer.Exit() - except (ConnectionClosed, SubstrateRequestException, KeyboardInterrupt) as e: + except ( + ConnectionClosed, + SubstrateRequestException, + KeyboardInterrupt, + ) as e: if isinstance(e, SubstrateRequestException): err_console.print(str(e)) raise typer.Exit() diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index 60ec9dce9..bd6bbb987 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -460,6 +460,9 @@ def __init__(self, chain, runtime_config, metadata, type_registry): self.runtime_config = runtime_config self.metadata = metadata + def __str__(self): + return f"Runtime: {self.chain} | {self.config}" + @property def implements_scaleinfo(self) -> bool: """ @@ -897,9 +900,10 @@ async def init_runtime( async def get_runtime(block_hash, block_id) -> Runtime: # Check if runtime state already set to current block - if (block_hash and block_hash == self.last_block_hash) or ( - block_id and block_id == self.block_id - ): + if ( + (block_hash and block_hash == self.last_block_hash) + or (block_id and block_id == self.block_id) + ) and self.metadata is not None: return Runtime( self.chain, self.runtime_config, @@ -945,9 +949,11 @@ async def get_runtime(block_hash, block_id) -> Runtime: raise SubstrateRequestException( f"No runtime information for block '{block_hash}'" ) - # Check if runtime state already set to current block - if runtime_info.get("specVersion") == self.runtime_version: + if ( + runtime_info.get("specVersion") == self.runtime_version + and self.metadata is not None + ): return Runtime( self.chain, self.runtime_config, @@ -962,16 +968,19 @@ async def get_runtime(block_hash, block_id) -> Runtime: if self.runtime_version in self.__metadata_cache: # Get metadata from cache # self.debug_message('Retrieved metadata for {} from memory'.format(self.runtime_version)) - self.metadata = self.__metadata_cache[self.runtime_version] + metadata = self.metadata = self.__metadata_cache[ + self.runtime_version + ] else: - self.metadata = await self.get_block_metadata( + metadata = self.metadata = await self.get_block_metadata( block_hash=runtime_block_hash, decode=True ) # self.debug_message('Retrieved metadata for {} from Substrate node'.format(self.runtime_version)) # Update metadata cache self.__metadata_cache[self.runtime_version] = self.metadata - + else: + metadata = self.metadata # Update type registry self.reload_type_registry(use_remote_preset=False, auto_discover=True) @@ -1012,7 +1021,10 @@ async def get_runtime(block_hash, block_id) -> Runtime: if block_id and block_hash: raise ValueError("Cannot provide block_hash and block_id at the same time") - if not (runtime := self.runtime_cache.retrieve(block_id, block_hash)): + if ( + not (runtime := self.runtime_cache.retrieve(block_id, block_hash)) + or runtime.metadata is None + ): runtime = await get_runtime(block_hash, block_id) self.runtime_cache.add_item(block_id, block_hash, runtime) return runtime @@ -1123,7 +1135,7 @@ async def create_storage_key( ------- StorageKey """ - await self.init_runtime(block_hash=block_hash) + runtime = await self.init_runtime(block_hash=block_hash) return StorageKey.create_from_storage_function( pallet, @@ -2272,7 +2284,7 @@ async def get_metadata_constant(self, module_name, constant_name, block_hash=Non MetadataModuleConstants """ - # await self.init_runtime(block_hash=block_hash) + await self.init_runtime(block_hash=block_hash) for module in self.metadata.pallets: if module_name == module.name and module.constants: From c5a01513aff68f59453674310ca9f932a2f8b44b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 18:24:09 +0200 Subject: [PATCH 04/11] CLI error handling cleanup --- bittensor_cli/cli.py | 14 +++++++------ .../src/bittensor/extrinsics/transfer.py | 6 +++++- .../src/bittensor/subtensor_interface.py | 20 +++++++++---------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index a95bef267..4c1e46171 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -822,17 +822,18 @@ def _run_command(self, cmd: Coroutine) -> None: """ async def _run(): - run_cmd = asyncio.create_task(cmd) + initiated = False try: if self.subtensor: async with self.subtensor: - result = await run_cmd + initiated = True + result = await cmd else: - result = await run_cmd + initiated = True + result = await cmd return result except (ConnectionRefusedError, ssl.SSLError): err_console.print(f"Unable to connect to the chain: {self.subtensor}") - raise typer.Exit() except ( ConnectionClosed, SubstrateRequestException, @@ -840,9 +841,10 @@ async def _run(): ) as e: if isinstance(e, SubstrateRequestException): err_console.print(str(e)) - raise typer.Exit() finally: - run_cmd.cancel() + if initiated is False: + asyncio.create_task(cmd).cancel() + raise typer.Exit() if sys.version_info < (3, 10): # For Python 3.9 or lower diff --git a/bittensor_cli/src/bittensor/extrinsics/transfer.py b/bittensor_cli/src/bittensor/extrinsics/transfer.py index ab2056802..eada5ec70 100644 --- a/bittensor_cli/src/bittensor/extrinsics/transfer.py +++ b/bittensor_cli/src/bittensor/extrinsics/transfer.py @@ -101,7 +101,11 @@ async def do_transfer() -> tuple[bool, str, str]: block_hash_ = response.block_hash return True, block_hash_, "" else: - return False, "", format_error_message(await response.error_message) + return ( + False, + "", + format_error_message(await response.error_message, subtensor.substrate), + ) # Validate destination address. if not is_valid_bittensor_address_or_public_key(destination): diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 62063c206..b056937aa 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -111,18 +111,18 @@ def __str__(self): return f"Network: {self.network}, Chain: {self.chain_endpoint}" async def __aenter__(self): - with console.status( - f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..." - ): - try: + try: + with console.status( + f"[yellow]Connecting to Substrate:[/yellow][bold white] {self}..." + ): async with self.substrate: return self - except TimeoutException: - err_console.print( - "\n[red]Error[/red]: Timeout occurred connecting to substrate. " - f"Verify your chain and network settings: {self}" - ) - raise typer.Exit(code=1) + except TimeoutException: + err_console.print( + "\n[red]Error[/red]: Timeout occurred connecting to substrate. " + f"Verify your chain and network settings: {self}" + ) + raise typer.Exit(code=1) async def __aexit__(self, exc_type, exc_val, exc_tb): await self.substrate.close() From db5447e0dba5a395415d242597bd5257c5538c3d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 19:38:19 +0200 Subject: [PATCH 05/11] Change error description output in format_error_message to use single quotes instead of backticks for easier formatting when pasting error. --- 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 e63807a83..3007ae59d 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -532,7 +532,7 @@ def format_error_message( err_docs = error_message.get("docs", [err_description]) err_description = err_docs[0] if err_docs else err_description - return f"Subtensor returned `{err_name}({err_type})` error. This means: `{err_description}`." + return f"Subtensor returned `{err_name}({err_type})` error. This means: '{err_description}'." def convert_blocks_to_time(blocks: int, block_time: int = 12) -> tuple[int, int, int]: From c0d296a8bfea90a552a446a67c7b4ad92a0657a0 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 30 Oct 2024 21:22:45 +0200 Subject: [PATCH 06/11] Fix taostats link --- 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 3007ae59d..b6b05baae 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -443,7 +443,7 @@ def get_explorer_url_for_network( explorer_opentensor_url = "{root_url}/query/{block_hash}".format( root_url=explorer_root_urls.get("opentensor"), block_hash=block_hash ) - explorer_taostats_url = "{root_url}/extrinsic/{block_hash}".format( + explorer_taostats_url = "{root_url}/hash/{block_hash}".format( root_url=explorer_root_urls.get("taostats"), block_hash=block_hash ) explorer_urls["opentensor"] = explorer_opentensor_url From 224b146a94a1c94c137faef649624580c43144cc Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 31 Oct 2024 14:10:41 -0700 Subject: [PATCH 07/11] Updates set-id tests --- tests/e2e_tests/test_wallet_interactions.py | 32 ++++++++++++++++----- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/tests/e2e_tests/test_wallet_interactions.py b/tests/e2e_tests/test_wallet_interactions.py index 8641f3698..996c5d7be 100644 --- a/tests/e2e_tests/test_wallet_interactions.py +++ b/tests/e2e_tests/test_wallet_interactions.py @@ -353,6 +353,7 @@ def test_wallet_identities(local_chain, wallet_setup): """ print("Testing wallet set-id, get-id, sign command ๐Ÿงช") + netuid = 1 wallet_path_alice = "//Alice" # Create wallet for Alice @@ -360,24 +361,41 @@ def test_wallet_identities(local_chain, wallet_setup): wallet_path_alice ) - # Register Alice to the root network (0) - # Either root list neurons can set-id or subnet owners - root_register = exec_command_alice( - command="root", - sub_command="register", + # Register a subnet with sudo as Alice + result = exec_command_alice( + command="subnets", + sub_command="create", extra_args=[ "--wallet-path", wallet_path_alice, - "--network", + "--chain", "ws://127.0.0.1:9945", "--wallet-name", wallet_alice.name, + "--no-prompt", + ], + ) + assert f"โœ… Registered subnetwork with netuid: {netuid}" in result.stdout + + # Register Alice in netuid = 1 using her hotkey + register_subnet = exec_command_alice( + command="subnets", + sub_command="register", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, "--hotkey", wallet_alice.hotkey_str, + "--netuid", + netuid, + "--chain", + "ws://127.0.0.1:9945", "--no-prompt", ], ) - assert "โœ… Registered" in root_register.stdout + assert "โœ… Registered" in register_subnet.stdout # Define values for Alice's identity alice_identity = { From e80e30bd42e44d76677105bc0913222231bb3b7f Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 31 Oct 2024 14:14:19 -0700 Subject: [PATCH 08/11] Adds config missing values --- bittensor_cli/cli.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 4c1e46171..7528f35c9 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -862,15 +862,32 @@ def main_callback( """ Command line interface (CLI) for Bittensor. Uses the values in the configuration file. These values can be overriden by passing them explicitly in the command line. """ - # create config file if it does not exist - if not os.path.exists(self.config_path): + # Load or create the config file + if os.path.exists(self.config_path): + with open(self.config_path, "r") as f: + config = safe_load(f) + else: directory_path = Path(self.config_base_path) directory_path.mkdir(exist_ok=True, parents=True) - with open(self.config_path, "w+") as f: - safe_dump(defaults.config.dictionary, f) - # check config - with open(self.config_path, "r") as f: - config = safe_load(f) + config = defaults.config.dictionary.copy() + with open(self.config_path, "w") as f: + safe_dump(config, f) + + # Update missing values + updated = False + for key, value in defaults.config.dictionary.items(): + if key not in config: + config[key] = value + updated = True + elif isinstance(value, dict): + for sub_key, sub_value in value.items(): + if sub_key not in config[key]: + config[key][sub_key] = sub_value + updated = True + if updated: + with open(self.config_path, "w") as f: + safe_dump(config, f) + for k, v in config.items(): if k in self.config.keys(): self.config[k] = v From 7de05d26cfd4d651b51c51c0a9b06bab30cfca3d Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 31 Oct 2024 15:13:03 -0700 Subject: [PATCH 09/11] Updates set-id test --- tests/e2e_tests/test_wallet_interactions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/e2e_tests/test_wallet_interactions.py b/tests/e2e_tests/test_wallet_interactions.py index 996c5d7be..c43efb90b 100644 --- a/tests/e2e_tests/test_wallet_interactions.py +++ b/tests/e2e_tests/test_wallet_interactions.py @@ -361,6 +361,25 @@ def test_wallet_identities(local_chain, wallet_setup): wallet_path_alice ) + # Register Alice to the root network (0) + # Either root list neurons + subnet registered can set-id or subnet owners + root_register = exec_command_alice( + command="root", + sub_command="register", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--no-prompt", + ], + ) + assert "โœ… Registered" in root_register.stdout + # Register a subnet with sudo as Alice result = exec_command_alice( command="subnets", From 9463baec1942ec19ab777b1448a0cb704b71ff1d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 1 Nov 2024 11:51:01 +0200 Subject: [PATCH 10/11] Fix sudo set. --- bittensor_cli/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 7528f35c9..a4575e919 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -816,7 +816,7 @@ def initialize_chain( self.subtensor = SubtensorInterface(defaults.subtensor.network) return self.subtensor - def _run_command(self, cmd: Coroutine) -> None: + def _run_command(self, cmd: Coroutine): """ Runs the supplied coroutine with `asyncio.run` """ @@ -844,7 +844,7 @@ async def _run(): finally: if initiated is False: asyncio.create_task(cmd).cancel() - raise typer.Exit() + raise typer.Exit() if sys.version_info < (3, 10): # For Python 3.9 or lower @@ -887,7 +887,7 @@ def main_callback( if updated: with open(self.config_path, "w") as f: safe_dump(config, f) - + for k, v in config.items(): if k in self.config.keys(): self.config[k] = v From 8843560c11bcc229ff86f61383663a2cf84cc90d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 1 Nov 2024 12:08:44 +0200 Subject: [PATCH 11/11] Fix no-prompt on confirmation for unstake --- 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 a4575e919..5008a03c5 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3481,7 +3481,7 @@ def stake_remove( if not unstake_all and not amount and not keep_stake: amount = FloatPrompt.ask("[blue bold]Amount to unstake (TAO ฯ„)[/blue bold]") - if unstake_all and not amount: + if unstake_all and not amount and prompt: if not Confirm.ask("Unstake all staked TAO tokens?", default=False): raise typer.Exit()