From 0fa6734692d200526c8f07b78a420be2533cf167 Mon Sep 17 00:00:00 2001 From: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Date: Thu, 26 Sep 2024 18:57:44 +0200 Subject: [PATCH 01/17] `root my-delegates` ask for path instead of name when using `--all` (#126) * Ask for wallet name for `root my-delegates` only if `--all` is not specified. If `--all` is specified, ask for the path. * Add validation. --- bittensor_cli/cli.py | 6 +++++- setup.py | 3 ++- tests/e2e_tests/test_senate.py | 6 +++--- tests/e2e_tests/utils.py | 12 +++++++++--- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 87cf5470..0bc86924 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2805,7 +2805,11 @@ def root_my_delegates( """ self.verbosity_handler(quiet, verbose) wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=([WO.NAME] if not all_wallets else [WO.PATH]), + validate=WV.WALLET if not all_wallets else WV.NONE ) self._run_command( root.my_delegates( diff --git a/setup.py b/setup.py index f5f23114..f9c92cda 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,8 @@ def read_requirements(path): long_description_content_type="text/markdown", url="https://github.com/opentensor/btcli", author="bittensor.com", - packages=find_packages(exclude=["tests", "tests.*", "*/tests/*", "*/tests"]) + ['bittensor_cli.src.bittensor.templates'], + packages=find_packages(exclude=["tests", "tests.*", "*/tests/*", "*/tests"]) + + ["bittensor_cli.src.bittensor.templates"], include_package_data=True, package_data={ "": ["templates/*"], diff --git a/tests/e2e_tests/test_senate.py b/tests/e2e_tests/test_senate.py index f3b3ac7f..83ad9082 100644 --- a/tests/e2e_tests/test_senate.py +++ b/tests/e2e_tests/test_senate.py @@ -88,7 +88,7 @@ def test_senate(local_chain, wallet_setup): # Assert Bob is now part of the senate assert wallet_bob.hotkey.ss58_address in root_senate_after_reg.stdout - + # Manually add a proposal on the chain & assert success = asyncio.run(call_add_proposal(local_chain, wallet_bob)) assert success is True @@ -154,10 +154,10 @@ def test_senate(local_chain, wallet_setup): assert proposals_after_aye_output[5] == "Aye" # Aye votes increased to 1 - assert proposals_after_aye_output[2] == '1' + assert proposals_after_aye_output[2] == "1" # Nay votes remain 0 - assert proposals_after_aye_output[3] == '0' + assert proposals_after_aye_output[3] == "0" # Register Alice to the root network (0) # Registering to root automatically makes you a senator if eligible diff --git a/tests/e2e_tests/utils.py b/tests/e2e_tests/utils.py index 53761e90..f5a70e70 100644 --- a/tests/e2e_tests/utils.py +++ b/tests/e2e_tests/utils.py @@ -11,7 +11,9 @@ from bittensor_wallet import Wallet if TYPE_CHECKING: - from bittensor_cli.src.bittensor.async_substrate_interface import AsyncSubstrateInterface + from bittensor_cli.src.bittensor.async_substrate_interface import ( + AsyncSubstrateInterface, + ) template_path = os.getcwd() + "/neurons/" templates_repo = "templates repository" @@ -287,7 +289,9 @@ def uninstall_templates(install_dir): shutil.rmtree(install_dir) -async def call_add_proposal(substrate: "AsyncSubstrateInterface", wallet: Wallet) -> bool: +async def call_add_proposal( + substrate: "AsyncSubstrateInterface", wallet: Wallet +) -> bool: async with substrate: proposal_call = await substrate.compose_call( call_module="System", @@ -304,7 +308,9 @@ async def call_add_proposal(substrate: "AsyncSubstrateInterface", wallet: Wallet }, ) - extrinsic = await substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey) + extrinsic = await substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) response = await substrate.submit_extrinsic( extrinsic, wait_for_inclusion=True, From 61daf9d1719646d80a4469e02d412041fe437126 Mon Sep 17 00:00:00 2001 From: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:44:43 +0200 Subject: [PATCH 02/17] Remove call to chain_finalised_head for metadata registry loading, handle SSL errors. (#127) --- bittensor_cli/cli.py | 5 ++++- .../bittensor/async_substrate_interface.py | 21 +++++++------------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 0bc86924..e58be306 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -3,6 +3,7 @@ import curses import os.path import re +import ssl import sys from pathlib import Path from typing import Coroutine, Optional @@ -756,16 +757,18 @@ async def _run(): else: result = await cmd return result - except ConnectionRefusedError: + except (ConnectionRefusedError, ssl.SSLError): err_console.print( f"Unable to connect to the chain: {self.not_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() raise typer.Exit() if sys.version_info < (3, 10): diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index 261cf167..fa33270b 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -845,11 +845,7 @@ async def _get_current_block_hash( async def load_registry(self): metadata_rpc_result = await self.rpc_request( "state_call", - [ - "Metadata_metadata_at_version", - self.metadata_version_hex, - await self.get_chain_finalised_head(), - ], + ["Metadata_metadata_at_version", self.metadata_version_hex], ) metadata_option_hex_str = metadata_rpc_result["result"] metadata_option_bytes = bytes.fromhex(metadata_option_hex_str[2:]) @@ -897,6 +893,7 @@ async def init_runtime( :returns: Runtime object """ + 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 ( @@ -2193,20 +2190,16 @@ async def runtime_call( params = {} try: - runtime_call_def = self.runtime_config.type_registry["runtime_api"][ - api - ]["methods"][method] + runtime_call_def = self.runtime_config.type_registry["runtime_api"][api][ + "methods" + ][method] runtime_api_types = self.runtime_config.type_registry["runtime_api"][ api ].get("types", {}) except KeyError: - raise ValueError( - f"Runtime API Call '{api}.{method}' not found in registry" - ) + raise ValueError(f"Runtime API Call '{api}.{method}' not found in registry") - if isinstance(params, list) and len(params) != len( - runtime_call_def["params"] - ): + if isinstance(params, list) and len(params) != len(runtime_call_def["params"]): raise ValueError( f"Number of parameter provided ({len(params)}) does not " f"match definition {len(runtime_call_def['params'])}" From 409a16f3b613bddefdea07b190bc943a17c97917 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor <165814940+ibraheem-opentensor@users.noreply.github.com> Date: Fri, 27 Sep 2024 07:56:32 -0700 Subject: [PATCH 03/17] Deprecate: Remove chain config (#128) --- bittensor_cli/cli.py | 257 +++++++----------- bittensor_cli/src/__init__.py | 1 - .../src/bittensor/subtensor_interface.py | 45 +-- bittensor_cli/src/bittensor/utils.py | 16 ++ tests/e2e_tests/test_root.py | 18 +- tests/e2e_tests/test_senate.py | 8 +- tests/e2e_tests/test_staking_sudo.py | 16 -- tests/e2e_tests/test_wallet_interactions.py | 24 -- 8 files changed, 147 insertions(+), 238 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index e58be306..5451891c 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -22,6 +22,7 @@ HELP_PANELS, WalletOptions as WO, WalletValidationTypes as WV, + Constants, ) from bittensor_cli.src.bittensor import utils from bittensor_cli.src.bittensor.async_substrate_interface import ( @@ -38,6 +39,7 @@ verbose_console, is_valid_ss58_address, print_error, + validate_chain_endpoint, ) from typing_extensions import Annotated from textwrap import dedent @@ -131,14 +133,9 @@ class Options: None, "--network", "--subtensor.network", - help="The subtensor network to connect to. Default: finney. Allowed values: local, test, finney.", - show_default=False, - ) - chain = typer.Option( - None, "--chain", "--subtensor.chain_endpoint", - help="The subtensor chain endpoint URL to connect to. The format should be similar to: ws://127.0.0.1:9946.", + help="The subtensor network to connect to. Default: finney.", show_default=False, ) netuids = typer.Option( @@ -398,7 +395,6 @@ def __init__(self): "wallet_path": None, "wallet_hotkey": None, "network": None, - "chain": None, "use_cache": True, "metagraph_cols": { "UID": True, @@ -714,7 +710,6 @@ def __init__(self): def initialize_chain( self, network: Optional[str] = None, - chain: Optional[str] = None, ) -> SubtensorInterface: """ Intelligently initializes a connection to the chain, depending on the supplied (or in config) values. Set's the @@ -724,24 +719,15 @@ def initialize_chain( :param chain: the chain endpoint (e.g. ws://127.0.0.1:9945, wss://entrypoint-finney.opentensor.ai:443, etc.) """ if not self.not_subtensor: - if network or chain: - self.not_subtensor = SubtensorInterface(network, chain) - elif self.config["network"] or self.config["chain"]: - self.not_subtensor = SubtensorInterface( - self.config["network"], self.config["chain"] + if network: + self.not_subtensor = SubtensorInterface(network) + elif self.config["network"]: + self.not_subtensor = SubtensorInterface(self.config["network"]) + console.print( + f"Using the specified network [dark_orange]{self.config['network']}[/dark_orange] from config" ) - if self.config["chain"]: - console.print( - f'Using the chain: [dark_orange]{self.config["chain"]}[/dark_orange] from config' - ) - else: - console.print( - f"Using the specified network [dark_orange]{self.config['network']}[/dark_orange] from config" - ) else: - self.not_subtensor = SubtensorInterface( - defaults.subtensor.network, defaults.subtensor.chain_endpoint - ) + self.not_subtensor = SubtensorInterface(defaults.subtensor.network) return self.not_subtensor def _run_command(self, cmd: Coroutine) -> None: @@ -849,7 +835,6 @@ def set_config( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, use_cache: Optional[bool] = typer.Option( None, "--cache/--no-cache", @@ -866,7 +851,6 @@ def set_config( "wallet_path": wallet_path, "wallet_hotkey": wallet_hotkey, "network": network, - "chain": chain, "use_cache": use_cache, } bools = ["use_cache"] @@ -900,31 +884,53 @@ def set_config( args[arg] = val self.config[arg] = val - if (n := args["network"]) and n.startswith("ws"): - if not Confirm.ask( - "[yellow]Warning:[/yellow] Unexpected input. You provided a chain endpoint URL to the 'network'. " - "Are you sure?" - ): - raise typer.Exit() - if (c := args["chain"]) and not c.startswith("ws"): - if not Confirm.ask( - "[yellow]Warning:[/yellow] Unexpected input. You provided a 'network' name to the chain endpoint. " - "Are you sure?" - ): - raise typer.Exit() + if n := args.get("network"): + if n in Constants.networks: + if not Confirm.ask( + f"You provided a network [dark_orange]{n}[/dark_orange] which is mapped to " + f"[dark_orange]{Constants.network_map[n]}[/dark_orange]\n" + "Do you want to continue?" + ): + typer.Exit() + else: + valid_endpoint, error = validate_chain_endpoint(n) + if valid_endpoint: + if valid_endpoint in Constants.network_map.values(): + known_network = next( + key for key, value in Constants.network_map.items() if value == network + ) + args["network"] = known_network + if not Confirm.ask( + f"You provided an endpoint [dark_orange]{n}[/dark_orange] which is mapped to " + f"[dark_orange]{known_network}[/dark_orange]\n" + "Do you want to continue?" + ): + typer.Exit() + else: + if not Confirm.ask( + f"You provided a chain endpoint URL [dark_orange]{n}[/dark_orange]\n" + "Do you want to continue?" + ): + raise typer.Exit() + else: + print_error(f"{error}") + raise typer.Exit() + for arg, val in args.items(): if val is not None: self.config[arg] = val with open(self.config_path, "w") as f: safe_dump(self.config, f) + # Print latest configs after updating + self.get_config() + def del_config( self, wallet_name: bool = typer.Option(False, *Options.wallet_name.param_decls), wallet_path: bool = typer.Option(False, *Options.wallet_path.param_decls), wallet_hotkey: bool = typer.Option(False, *Options.wallet_hotkey.param_decls), network: bool = typer.Option(False, *Options.network.param_decls), - chain: bool = typer.Option(False, *Options.chain.param_decls), use_cache: bool = typer.Option(False, "--cache"), all_items: bool = typer.Option(False, "--all"), ): @@ -954,7 +960,6 @@ def del_config( "wallet_path": wallet_path, "wallet_hotkey": wallet_hotkey, "network": network, - "chain": chain, "use_cache": use_cache, } @@ -1001,6 +1006,8 @@ def get_config(self): """ Prints the current config file in a table. """ + deprecated_configs = ["chain"] + table = Table( Column("[bold white]Name", style="dark_orange"), Column("[bold white]Value", style="gold1"), @@ -1009,8 +1016,16 @@ def get_config(self): ) for key, value in self.config.items(): - if key == "network" and value is None: - value = "None (default = finney)" + if key == "network": + if value is None: + value = "None (default = finney)" + else: + if value in Constants.networks: + value = value + f" ({Constants.network_map[value]})" + + elif key in deprecated_configs: + continue + if isinstance(value, dict): # Nested dictionaries: only metagraph for now, but more may be added later for idx, (sub_key, sub_value) in enumerate(value.items()): @@ -1022,7 +1037,7 @@ def get_config(self): console.print( dedent( """ - Note: The chain endpoint will take precedence over network. + [red]Deprecation notice[/red]: The chain endpoint config is now deprecated. You can use the network config to pass chain endpoints. """ ) ) @@ -1160,7 +1175,6 @@ def wallet_overview( ), netuids: str = Options.netuids, network: str = Options.network, - chain: str = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -1257,7 +1271,7 @@ def wallet_overview( return self._run_command( wallets.overview( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), all_wallets, sort_by, sort_order, @@ -1288,7 +1302,6 @@ def wallet_transfer( wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, network: str = Options.network, - chain: str = Options.chain, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -1323,7 +1336,7 @@ def wallet_transfer( ask_for=[WO.NAME], validate=WV.WALLET, ) - subtensor = self.initialize_chain(network, chain) + subtensor = self.initialize_chain(network) return self._run_command( wallets.transfer( wallet, subtensor, destination_ss58_address, amount, prompt @@ -1336,7 +1349,6 @@ def wallet_swap_hotkey( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, destination_hotkey_name: Optional[str] = typer.Argument( None, help="Destination hotkey name." ), @@ -1381,7 +1393,7 @@ def wallet_swap_hotkey( ask_for=[WO.NAME, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) - self.initialize_chain(network, chain) + self.initialize_chain(network) return self._run_command( wallets.swap_hotkey(original_wallet, new_wallet, self.not_subtensor, prompt) ) @@ -1399,7 +1411,6 @@ def wallet_inspect( wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, network: str = Options.network, - chain: str = Options.chain, netuids: str = Options.netuids, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -1450,7 +1461,7 @@ def wallet_inspect( wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate ) - self.initialize_chain(network, chain) + self.initialize_chain(network) return self._run_command( wallets.inspect( wallet, @@ -1466,7 +1477,6 @@ def wallet_faucet( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, # TODO add the following to config processors: Optional[int] = typer.Option( defaults.pow_register.num_processes, @@ -1541,7 +1551,7 @@ def wallet_faucet( return self._run_command( wallets.faucet( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), threads_per_block, update_interval, processors, @@ -1827,7 +1837,6 @@ def wallet_check_ck_swap( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -1844,7 +1853,7 @@ def wallet_check_ck_swap( """ self.verbosity_handler(quiet, verbose) wallet = self.wallet_ask(wallet_name, wallet_path, wallet_hotkey) - self.initialize_chain(network, chain) + self.initialize_chain(network) return self._run_command(wallets.check_coldkey_swap(wallet, self.not_subtensor)) def wallet_create_wallet( @@ -1914,7 +1923,6 @@ def wallet_balance( help="Whether to display the balances for all the wallets.", ), network: str = Options.network, - chain: str = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -1942,7 +1950,7 @@ def wallet_balance( wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate ) - subtensor = self.initialize_chain(network, chain) + subtensor = self.initialize_chain(network) return self._run_command( wallets.wallet_balance(wallet, subtensor, all_balances) ) @@ -1974,9 +1982,6 @@ def wallet_history( if self.config.get("network") != "finney": console.print(no_use_config_str) - elif self.config.get("chain"): - console.print(no_use_config_str) - self.verbosity_handler(quiet, verbose) wallet = self.wallet_ask( wallet_name, @@ -1993,7 +1998,6 @@ def wallet_set_id( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, display_name: str = typer.Option( "", "--display-name", @@ -2084,7 +2088,7 @@ def wallet_set_id( return self._run_command( wallets.set_id( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), display_name, legal_name, web_url, @@ -2110,7 +2114,6 @@ def wallet_get_id( prompt=True, ), network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -2137,7 +2140,7 @@ def wallet_get_id( self.verbosity_handler(quiet, verbose) return self._run_command( - wallets.get_id(self.initialize_chain(network, chain), target_ss58_address) + wallets.get_id(self.initialize_chain(network), target_ss58_address) ) def wallet_sign( @@ -2190,7 +2193,6 @@ def wallet_sign( def root_list( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -2209,13 +2211,12 @@ def root_list( """ self.verbosity_handler(quiet, verbose) return self._run_command( - root.root_list(subtensor=self.initialize_chain(network, chain)) + root.root_list(subtensor=self.initialize_chain(network)) ) def root_set_weights( self, network: str = Options.network, - chain: str = Options.chain, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -2286,14 +2287,13 @@ def root_set_weights( ) self._run_command( root.set_weights( - wallet, self.initialize_chain(network, chain), netuids, weights, prompt + wallet, self.initialize_chain(network), netuids, weights, prompt ) ) def root_get_weights( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, limit_min_col: Optional[int] = typer.Option( None, "--limit-min-col", @@ -2328,7 +2328,7 @@ def root_get_weights( ) raise typer.Exit() if not reuse_last: - subtensor = self.initialize_chain(network, chain) + subtensor = self.initialize_chain(network) else: subtensor = None return self._run_command( @@ -2345,7 +2345,6 @@ def root_get_weights( def root_boost( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: str = Options.wallet_name, wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, @@ -2379,14 +2378,13 @@ def root_boost( ) return self._run_command( root.set_boost( - wallet, self.initialize_chain(network, chain), netuid, amount, prompt + wallet, self.initialize_chain(network), netuid, amount, prompt ) ) def root_slash( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: str = Options.wallet_name, wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, @@ -2421,14 +2419,13 @@ def root_slash( ) return self._run_command( root.set_slash( - wallet, self.initialize_chain(network, chain), netuid, amount, prompt + wallet, self.initialize_chain(network), netuid, amount, prompt ) ) def root_senate_vote( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: Optional[str] = Options.wallet_name, wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, @@ -2472,14 +2469,13 @@ def root_senate_vote( ) return self._run_command( root.senate_vote( - wallet, self.initialize_chain(network, chain), proposal, vote, prompt + wallet, self.initialize_chain(network), proposal, vote, prompt ) ) def root_senate( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -2493,12 +2489,11 @@ def root_senate( [green]$[/green] btcli root senate """ self.verbosity_handler(quiet, verbose) - return self._run_command(root.get_senate(self.initialize_chain(network, chain))) + return self._run_command(root.get_senate(self.initialize_chain(network))) def root_register( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: Optional[str] = Options.wallet_name, wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, @@ -2528,13 +2523,12 @@ def root_register( validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( - root.register(wallet, self.initialize_chain(network, chain), prompt) + root.register(wallet, self.initialize_chain(network), prompt) ) def root_proposals( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -2548,12 +2542,11 @@ def root_proposals( [green]$[/green] btcli root proposals """ self.verbosity_handler(quiet, verbose) - return self._run_command(root.proposals(self.initialize_chain(network, chain))) + return self._run_command(root.proposals(self.initialize_chain(network))) def root_set_take( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: Optional[str] = Options.wallet_name, wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, @@ -2600,7 +2593,7 @@ def root_set_take( ) return self._run_command( - root.set_take(wallet, self.initialize_chain(network, chain), take) + root.set_take(wallet, self.initialize_chain(network), take) ) def root_delegate_stake( @@ -2624,7 +2617,6 @@ def root_delegate_stake( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -2672,7 +2664,7 @@ def root_delegate_stake( return self._run_command( root.delegate_stake( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), float(amount), delegate_ss58key, prompt, @@ -2700,7 +2692,6 @@ def root_undelegate_stake( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -2743,7 +2734,7 @@ def root_undelegate_stake( self._run_command( root.delegate_unstake( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), float(amount), delegate_ss58key, prompt, @@ -2753,7 +2744,6 @@ def root_undelegate_stake( def root_my_delegates( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: Optional[str] = Options.wallet_name, wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, @@ -2815,15 +2805,12 @@ def root_my_delegates( validate=WV.WALLET if not all_wallets else WV.NONE ) self._run_command( - root.my_delegates( - wallet, self.initialize_chain(network, chain), all_wallets - ) + root.my_delegates(wallet, self.initialize_chain(network), all_wallets) ) def root_list_delegates( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -2878,28 +2865,14 @@ def root_list_delegates( """ self.verbosity_handler(quiet, verbose) - if chain: - selected_network = network - selected_chain = chain - elif network: + if network: if network == "finney": - selected_network = "archive" - selected_chain = "wss://archive.chain.opentensor.ai:443" - else: - selected_network = network - selected_chain = chain - else: - chain_config = self.config.get("chain") - chain_network = self.config.get("network") + network = "wss://archive.chain.opentensor.ai:443" + elif self.config.get("network"): + if self.config.get("network") == "finney": + network = "wss://archive.chain.opentensor.ai:443" - if chain_network and chain_network == "finney" and not chain_config: - selected_network = "archive" - selected_chain = "wss://archive.chain.opentensor.ai:443" - else: - selected_network = chain_network - selected_chain = chain_config - - sub = self.initialize_chain(selected_network, selected_chain) + sub = self.initialize_chain(network) return self._run_command(root.list_delegates(sub)) # TODO: Confirm if we need a command for this - currently registering to root auto makes u delegate @@ -2909,7 +2882,6 @@ def root_nominate( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -2946,7 +2918,7 @@ def root_nominate( validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( - root.nominate(wallet, self.initialize_chain(network, chain), prompt) + root.nominate(wallet, self.initialize_chain(network), prompt) ) def stake_show( @@ -2959,7 +2931,6 @@ def stake_show( help="When set, the command checks all the coldkey wallets of the user instead of just the specified wallet.", ), network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: Optional[str] = Options.wallet_name, wallet_hotkey: Optional[str] = Options.wallet_hotkey, wallet_path: Optional[str] = Options.wallet_path, @@ -2999,7 +2970,7 @@ def stake_show( ) raise typer.Exit() if not reuse_last: - subtensor = self.initialize_chain(network, chain) + subtensor = self.initialize_chain(network) else: subtensor = None @@ -3071,7 +3042,6 @@ def stake_add( wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, network: str = Options.network, - chain: str = Options.chain, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3171,7 +3141,7 @@ def stake_add( return self._run_command( stake.stake_add( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), amount, stake_all, max_stake, @@ -3186,7 +3156,6 @@ def stake_add( def stake_remove( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -3327,7 +3296,7 @@ def stake_remove( return self._run_command( stake.unstake( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), hotkey_ss58_address, all_hotkeys, include_hotkeys, @@ -3345,7 +3314,6 @@ def stake_get_children( wallet_hotkey: Optional[str] = Options.wallet_hotkey, wallet_path: Optional[str] = Options.wallet_path, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, netuid: Optional[int] = typer.Option( None, help="The netuid of the subnet (e.g. 2)", @@ -3394,7 +3362,7 @@ def stake_get_children( return self._run_command( children_hotkeys.get_children( - wallet, self.initialize_chain(network, chain), netuid + wallet, self.initialize_chain(network), netuid ) ) @@ -3407,7 +3375,6 @@ def stake_set_children( wallet_hotkey: str = Options.wallet_hotkey, wallet_path: str = Options.wallet_path, network: str = Options.network, - chain: str = Options.chain, netuid: Optional[int] = typer.Option( None, help="The netuid of the subnet, (e.g. 4)", @@ -3484,7 +3451,7 @@ def stake_set_children( return self._run_command( children_hotkeys.set_children( wallet=wallet, - subtensor=self.initialize_chain(network, chain), + subtensor=self.initialize_chain(network), netuid=netuid, children=children, proportions=proportions, @@ -3499,7 +3466,6 @@ def stake_revoke_children( wallet_hotkey: Optional[str] = Options.wallet_hotkey, wallet_path: Optional[str] = Options.wallet_path, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, netuid: Optional[int] = typer.Option( None, help="The netuid of the subnet, (e.g. 8)", @@ -3546,7 +3512,7 @@ def stake_revoke_children( return self._run_command( children_hotkeys.revoke_children( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), netuid, wait_for_inclusion, wait_for_finalization, @@ -3559,7 +3525,6 @@ def stake_childkey_take( wallet_hotkey: Optional[str] = Options.wallet_hotkey, wallet_path: Optional[str] = Options.wallet_path, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, hotkey: Optional[str] = None, netuid: Optional[int] = typer.Option( None, @@ -3621,7 +3586,7 @@ def stake_childkey_take( return self._run_command( children_hotkeys.childkey_take( wallet=wallet, - subtensor=self.initialize_chain(network, chain), + subtensor=self.initialize_chain(network), netuid=netuid, take=take, hotkey=hotkey, @@ -3634,7 +3599,6 @@ def stake_childkey_take( def sudo_set( self, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -3660,7 +3624,7 @@ def sudo_set( self.verbosity_handler(quiet, verbose) hyperparams = self._run_command( - sudo.get_hyperparameters(self.initialize_chain(network, chain), netuid) + sudo.get_hyperparameters(self.initialize_chain(network), netuid) ) if not hyperparams: @@ -3690,7 +3654,7 @@ def sudo_set( return self._run_command( sudo.sudo_set_hyperparameter( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), netuid, param_name, param_value, @@ -3700,7 +3664,6 @@ def sudo_set( def sudo_get( self, network: str = Options.network, - chain: str = Options.chain, netuid: int = Options.netuid, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3716,13 +3679,12 @@ def sudo_get( """ self.verbosity_handler(quiet, verbose) return self._run_command( - sudo.get_hyperparameters(self.initialize_chain(network, chain), netuid) + sudo.get_hyperparameters(self.initialize_chain(network), netuid) ) def subnets_list( self, network: str = Options.network, - chain: str = Options.chain, reuse_last: bool = Options.reuse_last, html_output: bool = Options.html_output, quiet: bool = Options.quiet, @@ -3756,7 +3718,7 @@ def subnets_list( if reuse_last: subtensor = None else: - subtensor = self.initialize_chain(network, chain) + subtensor = self.initialize_chain(network) return self._run_command( subnets.subnets_list( subtensor, @@ -3769,7 +3731,6 @@ def subnets_list( def subnets_lock_cost( self, network: str = Options.network, - chain: str = Options.chain, quiet: bool = Options.quiet, verbose: bool = Options.verbose, ): @@ -3783,9 +3744,7 @@ def subnets_lock_cost( [green]$[/green] btcli subnets lock_cost """ self.verbosity_handler(quiet, verbose) - return self._run_command( - subnets.lock_cost(self.initialize_chain(network, chain)) - ) + return self._run_command(subnets.lock_cost(self.initialize_chain(network))) def subnets_create( self, @@ -3793,7 +3752,6 @@ def subnets_create( wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, network: str = Options.network, - chain: str = Options.chain, prompt: bool = Options.prompt, quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -3814,7 +3772,7 @@ def subnets_create( validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( - subnets.create(wallet, self.initialize_chain(network, chain), prompt) + subnets.create(wallet, self.initialize_chain(network), prompt) ) def subnets_pow_register( @@ -3823,7 +3781,6 @@ def subnets_pow_register( wallet_path: Optional[str] = Options.wallet_path, wallet_hotkey: Optional[str] = Options.wallet_hotkey, network: Optional[str] = Options.network, - chain: Optional[str] = Options.chain, netuid: int = Options.netuid, # TODO add the following to config processors: Optional[int] = typer.Option( @@ -3893,7 +3850,7 @@ def subnets_pow_register( ask_for=[WO.NAME, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ), - self.initialize_chain(network, chain), + self.initialize_chain(network), netuid, processors, update_interval, @@ -3910,7 +3867,6 @@ def subnets_register( wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, - chain: str = Options.chain, network: str = Options.network, netuid: int = Options.netuid, prompt: bool = Options.prompt, @@ -3939,7 +3895,7 @@ def subnets_register( return self._run_command( subnets.register( wallet, - self.initialize_chain(network, chain), + self.initialize_chain(network), netuid, prompt, ) @@ -3953,7 +3909,6 @@ def subnets_metagraph( "is ignored when used with `--reuse-last`.", ), network: str = Options.network, - chain: str = Options.chain, reuse_last: bool = Options.reuse_last, html_output: bool = Options.html_output, quiet: bool = Options.quiet, @@ -4028,7 +3983,7 @@ def subnets_metagraph( else: if netuid is None: netuid = rich.prompt.IntPrompt.ask("Enter the netuid (e.g. 1)") - subtensor = self.initialize_chain(network, chain) + subtensor = self.initialize_chain(network) return self._run_command( subnets.metagraph_cmd( @@ -4044,7 +3999,6 @@ def subnets_metagraph( def weights_reveal( self, network: str = Options.network, - chain: str = Options.chain, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -4128,7 +4082,7 @@ def weights_reveal( return self._run_command( weights_cmds.reveal_weights( - self.initialize_chain(network, chain), + self.initialize_chain(network), wallet, netuid, uids, @@ -4141,7 +4095,6 @@ def weights_reveal( def weights_commit( self, network: str = Options.network, - chain: str = Options.chain, wallet_name: str = Options.wallet_name, wallet_path: str = Options.wallet_path, wallet_hotkey: str = Options.wallet_hotkey, @@ -4224,7 +4177,7 @@ def weights_commit( ) return self._run_command( weights_cmds.commit_weights( - self.initialize_chain(network, chain), + self.initialize_chain(network), wallet, netuid, uids, diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index e9a52f58..a5a87e16 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -67,7 +67,6 @@ class config: path = "~/.bittensor/config.yml" dictionary = { "network": None, - "chain": None, "wallet_path": None, "wallet_name": None, "wallet_hotkey": None, diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 86a0a9a0..46a3a2c9 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -34,6 +34,7 @@ console, err_console, decode_hex_identity_dict, + validate_chain_endpoint ) @@ -69,33 +70,33 @@ class SubtensorInterface: Thin layer for interacting with Substrate Interface. Mostly a collection of frequently-used calls. """ - def __init__(self, network, chain_endpoint): - if chain_endpoint: - if not chain_endpoint.startswith("ws"): + def __init__(self, network): + if network in Constants.network_map: + self.chain_endpoint = Constants.network_map[network] + self.network = network + if network == "local": console.log( - "[yellow]Warning[/yellow]: Verify your chain endpoint is a valid substrate endpoint." + "[yellow]Warning[/yellow]: Verify your local subtensor is running on port 9944." ) - self.chain_endpoint = chain_endpoint - if chain_endpoint == Constants.network_map["archive"]: - self.network = "archive - finney" + else: + is_valid, _ = validate_chain_endpoint(network) + if is_valid: + self.chain_endpoint = network + if network in Constants.network_map.values(): + self.network = next( + key for key, value in Constants.network_map.items() if value == network + ) + else: + self.network = "custom" else: - self.network = "local" - elif network and network in Constants.network_map: - if network == "local": console.log( - "[yellow]Warning[/yellow]: Verify your local subtensor is running on 9944 port." + f"Network not specified or not valid. Using default chain endpoint: " + f"{Constants.network_map[defaults.subtensor.network]}.\n" + f"You can set this for commands with the `--network` flag, or by setting this" + f" in the config." ) - self.chain_endpoint = Constants.network_map[network] - self.network = network - else: - console.log( - f"Network not specified or not valid. Using default chain endpoint: " - f"{Constants.network_map[defaults.subtensor.network]}.\n" - f"You can set this for commands with the `--network` flag, or by setting this" - f" in the config." - ) - self.chain_endpoint = Constants.network_map[defaults.subtensor.network] - self.network = defaults.subtensor.network + self.chain_endpoint = Constants.network_map[defaults.subtensor.network] + self.network = defaults.subtensor.network self.substrate = AsyncSubstrateInterface( chain_endpoint=self.chain_endpoint, diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 7ddf8733..3c7db944 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -17,6 +17,7 @@ from scalecodec.base import RuntimeConfiguration from scalecodec.type_registry import load_type_registry_preset from bittensor_cli.src.bittensor.balances import Balance +from urllib.parse import urlparse if TYPE_CHECKING: from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters @@ -859,3 +860,18 @@ def group_subnets(registrations): ranges.append(f"{start}-{registrations[-1]}") return ", ".join(ranges) + + +def validate_chain_endpoint(endpoint_url) -> tuple[bool, str]: + parsed = urlparse(endpoint_url) + if parsed.scheme not in ("ws", "wss"): + return False, ( + f"Invalid URL or network name provided: [bright_cyan]({endpoint_url})[/bright_cyan].\n" + "Allowed network names are [bright_cyan]finney, test, local[/bright_cyan]. " + "Valid chain endpoints should use the scheme [bright_cyan]`ws` or `wss`[/bright_cyan].\n" + ) + if not parsed.netloc: + return False, "Invalid URL passed as the endpoint" + if not parsed.port: + return False, "No port specified in the URL" + return True, "" diff --git a/tests/e2e_tests/test_root.py b/tests/e2e_tests/test_root.py index 06514d24..85ca6b9d 100644 --- a/tests/e2e_tests/test_root.py +++ b/tests/e2e_tests/test_root.py @@ -50,14 +50,12 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--wallet-path", wallet_path_bob, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet_bob.name, "--hotkey", wallet_bob.hotkey_str, - "--network", - "local", "--no-prompt", ], ) @@ -70,8 +68,6 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--chain", "ws://127.0.0.1:9945", - "--network", - "local", ], ) @@ -95,8 +91,6 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--chain", "ws://127.0.0.1:9945", - "--network", - "local", ], ) @@ -139,8 +133,6 @@ def test_root_commands(local_chain, wallet_setup): wallet_bob.name, "--hotkey", wallet_bob.hotkey_str, - "--network", - "local", "--take", new_take, ], @@ -156,8 +148,6 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--chain", "ws://127.0.0.1:9945", - "--network", - "local", ], ) # Capture delegate information after setting take @@ -182,8 +172,6 @@ def test_root_commands(local_chain, wallet_setup): wallet_alice.name, "--delegate-ss58key", wallet_bob.hotkey.ss58_address, - "--network", - "local", "--amount", f"{delegate_amount}", "--no-prompt", @@ -202,8 +190,6 @@ def test_root_commands(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", wallet_alice.name, - "--network", - "local", ], ) # First row are headers, records start from second row @@ -248,8 +234,6 @@ def test_root_commands(local_chain, wallet_setup): wallet_alice.name, "--delegate-ss58key", wallet_bob.hotkey.ss58_address, - "--network", - "local", "--amount", f"{delegate_amount}", "--no-prompt", diff --git a/tests/e2e_tests/test_senate.py b/tests/e2e_tests/test_senate.py index 83ad9082..f335ada5 100644 --- a/tests/e2e_tests/test_senate.py +++ b/tests/e2e_tests/test_senate.py @@ -47,7 +47,7 @@ def test_senate(local_chain, wallet_setup): command="root", sub_command="senate", extra_args=[ - "--chain", + "--network", "ws://127.0.0.1:9945", ], ) @@ -63,14 +63,12 @@ def test_senate(local_chain, wallet_setup): extra_args=[ "--wallet-path", wallet_path_bob, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet_bob.name, "--hotkey", wallet_bob.hotkey_str, - "--network", - "local", "--no-prompt", ], ) @@ -173,8 +171,6 @@ def test_senate(local_chain, wallet_setup): wallet_alice.name, "--hotkey", wallet_alice.hotkey_str, - "--network", - "local", "--no-prompt", ], ) diff --git a/tests/e2e_tests/test_staking_sudo.py b/tests/e2e_tests/test_staking_sudo.py index 440a4f9e..16276486 100644 --- a/tests/e2e_tests/test_staking_sudo.py +++ b/tests/e2e_tests/test_staking_sudo.py @@ -51,8 +51,6 @@ def test_staking(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", wallet_alice.name, - "--network", - "local", "--no-prompt", ], ) @@ -69,8 +67,6 @@ def test_staking(local_chain, wallet_setup): wallet_alice.name, "--hotkey", wallet_alice.hotkey_str, - "--network", - "local", "--netuid", netuid, "--chain", @@ -91,8 +87,6 @@ def test_staking(local_chain, wallet_setup): wallet_alice.name, "--hotkey", wallet_alice.hotkey_str, - "--network", - "local", "--chain", "ws://127.0.0.1:9945", "--amount", @@ -111,8 +105,6 @@ def test_staking(local_chain, wallet_setup): wallet_path_alice, "--wallet-name", wallet_alice.name, - "--network", - "local", "--chain", "ws://127.0.0.1:9945", ], @@ -140,8 +132,6 @@ def test_staking(local_chain, wallet_setup): wallet_alice.name, "--hotkey", wallet_alice.hotkey_str, - "--network", - "local", "--chain", "ws://127.0.0.1:9945", "--amount", @@ -156,8 +146,6 @@ def test_staking(local_chain, wallet_setup): command="sudo", sub_command="get", extra_args=[ - "--network", - "local", "--chain", "ws://127.0.0.1:9945", "--netuid", @@ -183,8 +171,6 @@ def test_staking(local_chain, wallet_setup): wallet_alice.name, "--hotkey", wallet_alice.hotkey_str, - "--network", - "local", "--chain", "ws://127.0.0.1:9945", "--netuid", @@ -204,8 +190,6 @@ def test_staking(local_chain, wallet_setup): command="sudo", sub_command="get", extra_args=[ - "--network", - "local", "--chain", "ws://127.0.0.1:9945", "--netuid", diff --git a/tests/e2e_tests/test_wallet_interactions.py b/tests/e2e_tests/test_wallet_interactions.py index 27ad04b8..a1055e78 100644 --- a/tests/e2e_tests/test_wallet_interactions.py +++ b/tests/e2e_tests/test_wallet_interactions.py @@ -53,8 +53,6 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", wallet.name, - "--network", - "local", "--no-prompt", ], ) @@ -67,8 +65,6 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): extra_args=[ "--chain", "ws://127.0.0.1:9945", - "--network", - "local", ], ) @@ -86,8 +82,6 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): wallet.name, "--hotkey", wallet.hotkey_str, - "--network", - "local", "--netuid", "1", "--chain", @@ -108,8 +102,6 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", wallet.name, - "--network", - "local", ], ) @@ -132,8 +124,6 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): wallet_path, "--wallet-name", wallet.name, - "--network", - "local", "--chain", "ws://127.0.0.1:9945", ], @@ -158,8 +148,6 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): wallet_path, "--wallet-name", wallet.name, - "--network", - "local", "--chain", "ws://127.0.0.1:9945", ], @@ -218,8 +206,6 @@ def test_wallet_transfer(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", "default", - "--network", - "local", ], ) @@ -252,8 +238,6 @@ def test_wallet_transfer(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", "default", - "--network", - "local", "--amount", "100", "--no-prompt", @@ -273,8 +257,6 @@ def test_wallet_transfer(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", "default", - "--network", - "local", ], ) @@ -304,8 +286,6 @@ def test_wallet_transfer(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", "default", - "--network", - "local", ], ) @@ -339,8 +319,6 @@ def test_wallet_transfer(local_chain, wallet_setup): "ws://127.0.0.1:9945", "--wallet-name", "default", - "--network", - "local", "--amount", "100", "--no-prompt", @@ -402,8 +380,6 @@ def test_wallet_identities(local_chain, wallet_setup): wallet_alice.name, "--wallet-hotkey", wallet_alice.hotkey_str, - "--network", - "local", "--display-name", alice_identity["display_name"], "--legal-name", From 9c333d85531ac1c9a39625ca241feacc71f0bfb4 Mon Sep 17 00:00:00 2001 From: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:47:48 +0200 Subject: [PATCH 04/17] set archive node properly (#143) * Set archive network if network is not specified as well as if network specified as finney (through option or config) * Update tests to remove dual-use of network/chain, since they call the same thing. --- bittensor_cli/cli.py | 23 +++++--- .../src/bittensor/subtensor_interface.py | 6 +- bittensor_cli/src/commands/root.py | 25 +++++--- tests/e2e_tests/test_root.py | 58 ++++++++++--------- 4 files changed, 66 insertions(+), 46 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ea272b22..5ceeb0b4 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -712,11 +712,11 @@ def initialize_chain( network: Optional[str] = None, ) -> SubtensorInterface: """ - Intelligently initializes a connection to the chain, depending on the supplied (or in config) values. Set's the + Intelligently initializes a connection to the chain, depending on the supplied (or in config) values. Sets the `self.not_subtensor` object to this created connection. - :param network: Network name (e.g. finney, test, etc.) - :param chain: the chain endpoint (e.g. ws://127.0.0.1:9945, wss://entrypoint-finney.opentensor.ai:443, etc.) + :param network: Network name (e.g. finney, test, etc.) or + chain endpoint (e.g. ws://127.0.0.1:9945, wss://entrypoint-finney.opentensor.ai:443) """ if not self.not_subtensor: if network: @@ -897,7 +897,9 @@ def set_config( if valid_endpoint: if valid_endpoint in Constants.network_map.values(): known_network = next( - key for key, value in Constants.network_map.items() if value == network + key + for key, value in Constants.network_map.items() + if value == network ) args["network"] = known_network if not Confirm.ask( @@ -2802,7 +2804,7 @@ def root_my_delegates( wallet_path, wallet_hotkey, ask_for=([WO.NAME] if not all_wallets else [WO.PATH]), - validate=WV.WALLET if not all_wallets else WV.NONE + validate=WV.WALLET if not all_wallets else WV.NONE, ) self._run_command( root.my_delegates(wallet, self.initialize_chain(network), all_wallets) @@ -2860,7 +2862,7 @@ def root_list_delegates( [green]$[/green] btcli root list_delegates --subtensor.network finney # can also be `test` or `local` - [blue bold]NOTE[/blue bold]: This commmand is intended for use within a + [blue bold]NOTE[/blue bold]: This command is intended for use within a console application. It prints directly to the console and does not return any value. """ self.verbosity_handler(quiet, verbose) @@ -2868,9 +2870,12 @@ def root_list_delegates( if network: if network == "finney": network = "wss://archive.chain.opentensor.ai:443" - elif self.config.get("network"): - if self.config.get("network") == "finney": - network = "wss://archive.chain.opentensor.ai:443" + elif (conf_net := self.config.get("network")) == "finney": + network = "wss://archive.chain.opentensor.ai:443" + elif conf_net: + network = conf_net + else: + network = "wss://archive.chain.opentensor.ai:443" sub = self.initialize_chain(network) return self._run_command(root.list_delegates(sub)) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 46a3a2c9..246c01a6 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -34,7 +34,7 @@ console, err_console, decode_hex_identity_dict, - validate_chain_endpoint + validate_chain_endpoint, ) @@ -84,7 +84,9 @@ def __init__(self, network): self.chain_endpoint = network if network in Constants.network_map.values(): self.network = next( - key for key, value in Constants.network_map.items() if value == network + key + for key, value in Constants.network_map.items() + if value == network ) else: self.network = "custom" diff --git a/bittensor_cli/src/commands/root.py b/bittensor_cli/src/commands/root.py index 716602af..c9d8f924 100644 --- a/bittensor_cli/src/commands/root.py +++ b/bittensor_cli/src/commands/root.py @@ -490,13 +490,19 @@ async def _do_delegation(staking_balance_: Balance) -> tuple[bool, str]: call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="add_stake", - call_params={"hotkey": delegate_ss58, "amount_staked": staking_balance_.rao}, + call_params={ + "hotkey": delegate_ss58, + "amount_staked": staking_balance_.rao, + }, ) else: call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="remove_stake", - call_params={"hotkey": delegate_ss58, "amount_unstaked": staking_balance_.rao}, + call_params={ + "hotkey": delegate_ss58, + "amount_unstaked": staking_balance_.rao, + }, ) return await subtensor.sign_and_send_extrinsic( call, wallet, wait_for_inclusion, wait_for_finalization @@ -576,7 +582,7 @@ async def get_stake_for_coldkey_and_hotkey( staking_balance = Balance.from_tao(amount) # Check enough balance to stake. - if delegate_string == "delegate" and staking_balance > my_prev_coldkey_balance: + if delegate_string == "delegate" and staking_balance > my_prev_coldkey_balance: err_console.print( ":cross_mark: [red]Not enough balance to stake[/red]:\n" f" [bold blue]current balance[/bold blue]:{my_prev_coldkey_balance}\n" @@ -726,21 +732,22 @@ async def _get_list() -> tuple: ) return sm, rn, di, ts - with console.status( f":satellite: Syncing with chain: [white]{subtensor}[/white] ...", spinner="aesthetic", ): - senate_members, root_neurons, delegate_info, total_stakes = await _get_list() - total_tao = sum(float(Balance.from_rao(total_stakes[neuron.hotkey])) for neuron in root_neurons) + total_tao = sum( + float(Balance.from_rao(total_stakes[neuron.hotkey])) + for neuron in root_neurons + ) table = Table( Column( "[bold white]UID", style="dark_orange", no_wrap=True, - footer=f"[bold]{len(root_neurons)}[/bold]" + footer=f"[bold]{len(root_neurons)}[/bold]", ), Column( "[bold white]NAME", @@ -757,7 +764,7 @@ async def _get_list() -> tuple: justify="right", style="light_goldenrod2", no_wrap=True, - footer=f"{total_tao:.2f} (\u03c4) " + footer=f"{total_tao:.2f} (\u03c4) ", ), Column( "[bold white]SENATOR", @@ -777,7 +784,7 @@ async def _get_list() -> tuple: f"[red]Error: No neurons detected on the network:[/red] [white]{subtensor}" ) raise typer.Exit() - + sorted_root_neurons = sorted( root_neurons, key=lambda neuron: float(Balance.from_rao(total_stakes[neuron.hotkey])), diff --git a/tests/e2e_tests/test_root.py b/tests/e2e_tests/test_root.py index 7fe4432a..26485c25 100644 --- a/tests/e2e_tests/test_root.py +++ b/tests/e2e_tests/test_root.py @@ -70,7 +70,7 @@ def test_root_commands(local_chain, wallet_setup): command="root", sub_command="list", extra_args=[ - "--chain", + "--network", "ws://127.0.0.1:9945", ], ) @@ -93,7 +93,7 @@ def test_root_commands(local_chain, wallet_setup): command="root", sub_command="list-delegates", extra_args=[ - "--chain", + "--network", "ws://127.0.0.1:9945", ], ) @@ -131,7 +131,7 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--wallet-path", wallet_path_bob, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet_bob.name, @@ -150,7 +150,7 @@ def test_root_commands(local_chain, wallet_setup): command="root", sub_command="list-delegates", extra_args=[ - "--chain", + "--network", "ws://127.0.0.1:9945", ], ) @@ -168,14 +168,14 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--wallet-path", wallet_path_alice, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet_alice.name, "--delegate-ss58key", wallet_bob.hotkey.ss58_address, "--amount", - f"10", + "10", "--no-prompt", ], ) @@ -185,13 +185,17 @@ def test_root_commands(local_chain, wallet_setup): exec_command=exec_command_alice, wallet=wallet_alice, delegate_ss58key=wallet_bob.hotkey.ss58_address, - delegate_amount=10 + delegate_amount=10, ) check_balance( exec_command=exec_command_alice, wallet=wallet_alice, - expected_balance={'free_balance': 999990.0, 'staked_balance': 10.0, 'total_balance': 1000000.0}, + expected_balance={ + "free_balance": 999990.0, + "staked_balance": 10.0, + "total_balance": 1000000.0, + }, ) # TODO: Ask nucleus the rate limit and wait epoch @@ -206,14 +210,12 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--wallet-path", wallet_path_alice, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet_alice.name, "--delegate-ss58key", wallet_bob.hotkey.ss58_address, - "--network", - "local", "--amount", f"10", "--no-prompt", @@ -224,7 +226,11 @@ def test_root_commands(local_chain, wallet_setup): check_balance( exec_command=exec_command_alice, wallet=wallet_alice, - expected_balance={'free_balance': 1000000.0, 'staked_balance': 0.0, 'total_balance': 1000000.0}, + expected_balance={ + "free_balance": 1000000.0, + "staked_balance": 0.0, + "total_balance": 1000000.0, + }, ) # TODO: Ask nucleus the rate limit and wait epoch @@ -239,14 +245,12 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--wallet-path", wallet_path_alice, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet_alice.name, "--delegate-ss58key", wallet_bob.hotkey.ss58_address, - "--network", - "local", "--all", "--no-prompt", ], @@ -263,7 +267,11 @@ def test_root_commands(local_chain, wallet_setup): check_balance( exec_command=exec_command_alice, wallet=wallet_alice, - expected_balance={'free_balance': 0.0000005, 'staked_balance': 999999.9999995, 'total_balance': 1000000.0}, + expected_balance={ + "free_balance": 0.0000005, + "staked_balance": 999999.9999995, + "total_balance": 1000000.0, + }, ) # TODO: Ask nucleus the rate limit and wait epoch @@ -278,14 +286,12 @@ def test_root_commands(local_chain, wallet_setup): extra_args=[ "--wallet-path", wallet_path_alice, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet_alice.name, "--delegate-ss58key", wallet_bob.hotkey.ss58_address, - "--network", - "local", "--all", "--no-prompt", ], @@ -295,7 +301,11 @@ def test_root_commands(local_chain, wallet_setup): check_balance( exec_command=exec_command_alice, wallet=wallet_alice, - expected_balance={'free_balance': 1000000.0, 'staked_balance': 0.0, 'total_balance': 1000000.0}, + expected_balance={ + "free_balance": 1000000.0, + "staked_balance": 0.0, + "total_balance": 1000000.0, + }, ) print("✅ Passed Root commands") @@ -309,12 +319,10 @@ def check_my_delegates(exec_command, wallet, delegate_ss58key, delegate_amount): extra_args=[ "--wallet-path", wallet.path, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet.name, - "--network", - "local", ], ) # First row are headers, records start from second row @@ -345,12 +353,10 @@ def check_balance(exec_command, wallet, expected_balance): extra_args=[ "--wallet-path", wallet.path, - "--chain", + "--network", "ws://127.0.0.1:9945", "--wallet-name", wallet.name, - "--network", - "local", ], ) From edd3764b41dc7f6c52bfe5e2fd75ca32b97de823 Mon Sep 17 00:00:00 2001 From: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:26:34 +0200 Subject: [PATCH 05/17] Randomise rpc request ID (#131) * Randomise rpc_request id to avoid possible dict duplicate keys. --- bittensor_cli/cli.py | 2 ++ .../src/bittensor/async_substrate_interface.py | 12 +++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 5ceeb0b4..4c40e0a6 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1155,10 +1155,12 @@ def wallet_overview( ), sort_by: Optional[str] = typer.Option( None, + "--sort-by", "--sort_by", help="Sort the hotkeys by the specified column title. For example: name, uid, axon.", ), sort_order: Optional[str] = typer.Option( None, + "--sort-order", "--sort_order", help="Sort the hotkeys in the specified order (ascending/asc or descending/desc/reverse).", ), include_hotkeys: str = typer.Option( diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index fa33270b..c38aca13 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -1,5 +1,6 @@ import asyncio import json +import random from collections import defaultdict from dataclasses import dataclass from hashlib import blake2b @@ -1690,9 +1691,10 @@ async def rpc_request( """ block_hash = await self._get_current_block_hash(block_hash, reuse_block_hash) params = params or [] + payload_id = f"{method}{random.randint(0, 7000)}" payloads = [ self.make_payload( - "rpc_request", + payload_id, method, params + [block_hash] if block_hash else params, ) @@ -1704,14 +1706,14 @@ async def rpc_request( self.type_registry, ) result = await self._make_rpc_request(payloads, runtime=runtime) - if "error" in result["rpc_request"][0]: + if "error" in result[payload_id][0]: raise SubstrateRequestException( result["rpc_request"][0]["error"]["message"] ) - if "result" in result["rpc_request"][0]: - return result["rpc_request"][0] + if "result" in result[payload_id][0]: + return result[payload_id][0] else: - raise SubstrateRequestException(result["rpc_request"][0]) + raise SubstrateRequestException(result[payload_id][0]) async def get_block_hash(self, block_id: int) -> str: return (await self.rpc_request("chain_getBlockHash", [block_id]))["result"] From 5c7bf0ab6bf1131aa6d93a03fcd71704d6c669ac Mon Sep 17 00:00:00 2001 From: Doug Sillars Date: Tue, 1 Oct 2024 14:41:30 -0400 Subject: [PATCH 06/17] update help text in the BTCLI (#139) * root register message * set-weights help message valis on root set weights.. not just senators. * remove --wallet-name form list delegates examples I think this is covered in my-delegates, and the help text was not removed from llist-delegates * fix spelling on change --- bittensor_cli/cli.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 4c40e0a6..d4417bb7 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -2239,7 +2239,7 @@ def root_set_weights( """ Set the weights for different subnets, by setting them in the root network. - To use this command, you should specify the netuids and corresponding weights you wish to assign. This command is used by network senators to influence the distribution of subnet rewards and responsibilities. + To use this command, you should specify the netuids and corresponding weights you wish to assign. This command is used by validators registered to the root subnet to influence the distribution of subnet rewards and responsibilities. You must have a comprehensive understanding of the dynamics of the subnets to use this command. It is a powerful tool that directly impacts the subnet's operational mechanics and reward distribution. @@ -2506,9 +2506,9 @@ def root_register( verbose: bool = Options.verbose, ): """ - Register a neuron (a subnet validator or a subnet miner) to a specified subnet by recycling some TAO to cover for the registration cost. + Register a neuron to the root subnet by recycling some TAO to cover for the registration cost. - This command adds a new neuron (a subnet validator or a subnet miner) to a specified subnet, contributing to the decentralization and robustness of Bittensor. + This command adds a new neuron as a validator on the root network. This will allow the neuron owner to set subnet weights. # Usage: @@ -2860,8 +2860,6 @@ def root_list_delegates( [green]$[/green] btcli root list_delegates - [green]$[/green] btcli root list_delegates --wallet-name my_wallet - [green]$[/green] btcli root list_delegates --subtensor.network finney # can also be `test` or `local` [blue bold]NOTE[/blue bold]: This command is intended for use within a From 1423066302b3f964319d3a890938b2a22aaae11e Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor <165814940+ibraheem-opentensor@users.noreply.github.com> Date: Tue, 1 Oct 2024 13:42:14 -0700 Subject: [PATCH 07/17] Added the `--help` message automatically when no command is specified, both for `btcli` itself, and all major commands (not subcommands). (#145) Co-authored-by: Benjamin Himes Co-authored-by: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor_cli/cli.py | 51 +++++++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d4417bb7..af9dfdcb 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -419,7 +419,10 @@ def __init__(self): self.config_path = os.path.expanduser(defaults.config.path) self.app = typer.Typer( - rich_markup_mode="rich", callback=self.main_callback, epilog=_epilog + rich_markup_mode="rich", + callback=self.main_callback, + epilog=_epilog, + no_args_is_help=True, ) self.config_app = typer.Typer(epilog=_epilog) self.wallet_app = typer.Typer(epilog=_epilog) @@ -434,51 +437,65 @@ def __init__(self): self.config_app, name="config", short_help="Config commands, aliases: `c`, `conf`", + no_args_is_help=True, ) - self.app.add_typer(self.config_app, name="conf", hidden=True) - self.app.add_typer(self.config_app, name="c", hidden=True) + self.app.add_typer( + self.config_app, name="conf", hidden=True, no_args_is_help=True + ) + self.app.add_typer(self.config_app, name="c", hidden=True, no_args_is_help=True) # wallet aliases self.app.add_typer( self.wallet_app, name="wallet", short_help="Wallet commands, aliases: `wallets`, `w`", + no_args_is_help=True, + ) + self.app.add_typer(self.wallet_app, name="w", hidden=True, no_args_is_help=True) + self.app.add_typer( + self.wallet_app, name="wallets", hidden=True, no_args_is_help=True ) - self.app.add_typer(self.wallet_app, name="w", hidden=True) - self.app.add_typer(self.wallet_app, name="wallets", hidden=True) # root aliases self.app.add_typer( self.root_app, name="root", short_help="Root commands, alias: `r`", + no_args_is_help=True, ) - self.app.add_typer(self.root_app, name="r", hidden=True) + self.app.add_typer(self.root_app, name="r", hidden=True, no_args_is_help=True) # stake aliases self.app.add_typer( self.stake_app, name="stake", short_help="Stake commands, alias: `st`", + no_args_is_help=True, ) - self.app.add_typer(self.stake_app, name="st", hidden=True) + self.app.add_typer(self.stake_app, name="st", hidden=True, no_args_is_help=True) # sudo aliases self.app.add_typer( self.sudo_app, name="sudo", short_help="Sudo commands, alias: `su`", + no_args_is_help=True, ) - self.app.add_typer(self.sudo_app, name="su", hidden=True) + self.app.add_typer(self.sudo_app, name="su", hidden=True, no_args_is_help=True) # subnets aliases self.app.add_typer( self.subnets_app, name="subnets", short_help="Subnets commands, alias: `s`, `subnet`", + no_args_is_help=True, + ) + self.app.add_typer( + self.subnets_app, name="s", hidden=True, no_args_is_help=True + ) + self.app.add_typer( + self.subnets_app, name="subnet", hidden=True, no_args_is_help=True ) - self.app.add_typer(self.subnets_app, name="s", hidden=True) - self.app.add_typer(self.subnets_app, name="subnet", hidden=True) # weights aliases self.app.add_typer( @@ -486,9 +503,14 @@ def __init__(self): name="weights", short_help="Weights commands, aliases: `wt`, `weight`", hidden=True, + no_args_is_help=True, + ) + self.app.add_typer( + self.weights_app, name="wt", hidden=True, no_args_is_help=True + ) + self.app.add_typer( + self.weights_app, name="weight", hidden=True, no_args_is_help=True ) - self.app.add_typer(self.weights_app, name="wt", hidden=True) - self.app.add_typer(self.weights_app, name="weight", hidden=True) # config commands self.config_app.command("set")(self.set_config) @@ -610,8 +632,11 @@ def __init__(self): name="child", short_help="Child Hotkey commands, alias: `children`", rich_help_panel=HELP_PANELS["STAKE"]["CHILD"], + no_args_is_help=True, + ) + self.stake_app.add_typer( + children_app, name="children", hidden=True, no_args_is_help=True ) - self.stake_app.add_typer(children_app, name="children", hidden=True) children_app.command("get")(self.stake_get_children) children_app.command("set")(self.stake_set_children) children_app.command("revoke")(self.stake_revoke_children) From 87bbf02a9dc3e7fb4236f9206579fa1dac03b262 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Tue, 1 Oct 2024 16:56:41 -0700 Subject: [PATCH 08/17] Bumps version --- bittensor_cli/__init__.py | 2 +- bittensor_cli/cli.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor_cli/__init__.py b/bittensor_cli/__init__.py index 746e05b1..84e1ab21 100644 --- a/bittensor_cli/__init__.py +++ b/bittensor_cli/__init__.py @@ -18,6 +18,6 @@ from .cli import CLIManager -__version__ = "8.0.0" +__version__ = "8.1.0" __all__ = ["CLIManager", "__version__"] diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index af9dfdcb..79691758 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -46,7 +46,7 @@ from websockets import ConnectionClosed from yaml import safe_dump, safe_load -__version__ = "8.0.0" +__version__ = "8.1.0" _core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0) _version_split = _core_version.split(".") From 541f0519ecf7f3049bc5ceb8caf28e6a02d146cf Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor <165814940+ibraheem-opentensor@users.noreply.github.com> Date: Wed, 2 Oct 2024 01:38:18 -0700 Subject: [PATCH 09/17] Backmerge main to staging (#147) * Added the `--help` message automatically when no command is specified, both for `btcli` itself, and all major commands (not subcommands). * Adding PyPI badge --------- Co-authored-by: Benjamin Himes Co-authored-by: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Co-authored-by: Watchmaker --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 94eb7f38..bd71dfe5 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ # Bittensor CLI [![Discord Chat](https://img.shields.io/discord/308323056592486420.svg)](https://discord.gg/bittensor) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) - +[![PyPI version](https://badge.fury.io/py/bittensor_cli.svg)](https://badge.fury.io/py/bittensor_cli) --- @@ -140,4 +140,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From 5ce9bfc757398cbf3788c2870091d457ad5c7af0 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor <165814940+ibraheem-opentensor@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:58:05 -0700 Subject: [PATCH 10/17] Updates "btcli w set-identity" (#146) * Improves set-id command * Ruff * Fixes prompt in-case of undelegation * Fixes string type * WIP * Updates tests for set-id * return False in-case of failure * Retry Prompt Logic (#151) * Added retry prompt for use in set-id to allow for checking the input as it's entered for rejection/acceptance later. * Updated helper text in set_id_prompts * Use retry_prompt in set_id_prompts * Makes pgp and twitter fields optional * Fixed indentation issue * Handles edge cases and removes prompt for is_validator in nominate + subnet create * pgp validation false * Reverts encoding in set_id --------- Co-authored-by: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Co-authored-by: Benjamin Himes --- bittensor_cli/cli.py | 97 ++++++++++++++--- .../src/bittensor/subtensor_interface.py | 17 +++ bittensor_cli/src/bittensor/utils.py | 32 +++++- bittensor_cli/src/commands/root.py | 8 +- bittensor_cli/src/commands/stake/stake.py | 27 +---- bittensor_cli/src/commands/subnets.py | 2 +- bittensor_cli/src/commands/wallets.py | 103 ++++++++++++++---- tests/e2e_tests/test_wallet_interactions.py | 23 +++- 8 files changed, 240 insertions(+), 69 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index af9dfdcb..45e6379c 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 import asyncio +import binascii import curses +from functools import partial import os.path import re import ssl @@ -40,6 +42,7 @@ is_valid_ss58_address, print_error, validate_chain_endpoint, + retry_prompt, ) from typing_extensions import Annotated from textwrap import dedent @@ -1180,12 +1183,14 @@ def wallet_overview( ), sort_by: Optional[str] = typer.Option( None, - "--sort-by", "--sort_by", + "--sort-by", + "--sort_by", help="Sort the hotkeys by the specified column title. For example: name, uid, axon.", ), sort_order: Optional[str] = typer.Option( None, - "--sort-order", "--sort_order", + "--sort-order", + "--sort_order", help="Sort the hotkeys in the specified order (ascending/asc or descending/desc/reverse).", ), include_hotkeys: str = typer.Option( @@ -2032,44 +2037,46 @@ def wallet_set_id( "--display-name", "--display", help="The display name for the identity.", - prompt=True, ), legal_name: str = typer.Option( "", "--legal-name", "--legal", help="The legal name for the identity.", - prompt=True, ), web_url: str = typer.Option( - "", "--web-url", "--web", help="The web URL for the identity.", prompt=True + "", + "--web-url", + "--web", + help="The web URL for the identity.", ), riot_handle: str = typer.Option( "", "--riot-handle", "--riot", help="The Riot handle for the identity.", - prompt=True, ), email: str = typer.Option( - "", help="The email address for the identity.", prompt=True + "", + help="The email address for the identity.", ), pgp_fingerprint: str = typer.Option( "", "--pgp-fingerprint", "--pgp", help="The PGP fingerprint for the identity.", - prompt=True, ), image_url: str = typer.Option( "", "--image-url", "--image", help="The image URL for the identity.", - prompt=True, ), info_: str = typer.Option( - "", "--info", "-i", help="The info for the identity.", prompt=True + "", + "--info", + "-i", + help="The info for the identity.", ), twitter_url: str = typer.Option( "", @@ -2078,12 +2085,16 @@ def wallet_set_id( "--twitter-url", "--twitter", help="The 𝕏 (Twitter) URL for the identity.", - prompt=True, ), - validator_id: bool = typer.Option( + validator_id: Optional[bool] = typer.Option( + None, "--validator/--not-validator", help="Are you updating a validator hotkey identity?", - prompt=True, + ), + subnet_netuid: Optional[int] = typer.Option( + None, + "--netuid", + help="Netuid if you are updating identity of a subnet owner", ), quiet: bool = Options.quiet, verbose: bool = Options.verbose, @@ -2104,7 +2115,7 @@ def wallet_set_id( [green]$[/green] btcli wallet set_identity - [bold]Note[/bold]: This command should only be used if the user is willing to incur the 1 TAO transaction fee associated with setting an identity on the blockchain. It is a high-level command that makes changes to the blockchain state and should not be used programmatically as part of other scripts or applications. + [bold]Note[/bold]: This command should only be used if the user is willing to incur the a recycle fee associated with setting an identity on the blockchain. It is a high-level command that makes changes to the blockchain state and should not be used programmatically as part of other scripts or applications. """ self.verbosity_handler(quiet, verbose) wallet = self.wallet_ask( @@ -2114,6 +2125,63 @@ def wallet_set_id( ask_for=[WO.HOTKEY, WO.NAME], validate=WV.WALLET_AND_HOTKEY, ) + + if not any( + [ + display_name, + legal_name, + web_url, + riot_handle, + email, + pgp_fingerprint, + image_url, + info_, + twitter_url, + ] + ): + console.print( + "[yellow]All fields are optional. Press Enter to skip a field.[/yellow]" + ) + text_rejection = partial( + retry_prompt, + rejection=lambda x: sys.getsizeof(x) > 113, + rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.", + ) + + def pgp_check(s: str): + try: + if s.startswith("0x"): + s = s[2:] # Strip '0x' + pgp_fingerprint_encoded = binascii.unhexlify(s.replace(" ", "")) + except Exception: + return True + return True if len(pgp_fingerprint_encoded) != 20 else False + + display_name = display_name or text_rejection("Display name") + legal_name = legal_name or text_rejection("Legal name") + web_url = web_url or text_rejection("Web URL") + riot_handle = riot_handle or text_rejection("Riot handle") + email = email or text_rejection("Email address") + pgp_fingerprint = pgp_fingerprint or retry_prompt( + "PGP fingerprint (Eg: A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678)", + lambda s: False if not s else pgp_check(s), + "[red]Error:[/red] PGP Fingerprint must be exactly 20 bytes.", + ) + image_url = image_url or text_rejection("Image URL") + info_ = info_ or text_rejection("Enter info") + twitter_url = twitter_url or text_rejection("𝕏 (Twitter) URL") + + validator_id = validator_id or Confirm.ask( + "Are you updating a [bold blue]validator hotkey[/bold blue] identity or a [bold blue]subnet " + "owner[/bold blue] identity?\n" + "Enter [bold green]Y[/bold green] for [bold]validator hotkey[/bold] or [bold red]N[/bold red] for " + "[bold]subnet owner[/bold]", + show_choices=True, + ) + + if validator_id is False: + subnet_netuid = IntPrompt.ask("Enter the netuid of the subnet you own") + return self._run_command( wallets.set_id( wallet, @@ -2129,6 +2197,7 @@ def wallet_set_id( info_, validator_id, prompt, + subnet_netuid, ) ) diff --git a/bittensor_cli/src/bittensor/subtensor_interface.py b/bittensor_cli/src/bittensor/subtensor_interface.py index 246c01a6..f53c97d9 100644 --- a/bittensor_cli/src/bittensor/subtensor_interface.py +++ b/bittensor_cli/src/bittensor/subtensor_interface.py @@ -877,6 +877,23 @@ async def does_hotkey_exist( ) return return_val + async def get_hotkey_owner( + self, hotkey_ss58: str, block_hash: str + ) -> Optional[str]: + hk_owner_query = await self.substrate.query( + module="SubtensorModule", + storage_function="Owner", + params=[hotkey_ss58], + block_hash=block_hash, + ) + val = decode_account_id(hk_owner_query[0]) + if val: + exists = await self.does_hotkey_exist(hotkey_ss58, block_hash=block_hash) + else: + exists = False + hotkey_owner = val if exists else None + return hotkey_owner + async def sign_and_send_extrinsic( self, call: GenericCall, diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 3c7db944..f42dcbdf 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -3,10 +3,11 @@ import sqlite3 import webbrowser from pathlib import Path -from typing import TYPE_CHECKING, Any, Collection, Optional, Union +from typing import TYPE_CHECKING, Any, Collection, Optional, Union, Callable import numpy as np import scalecodec +import typer from bittensor_wallet import Wallet from bittensor_wallet.keyfile import Keypair from bittensor_wallet.utils import SS58_FORMAT, ss58 @@ -875,3 +876,32 @@ def validate_chain_endpoint(endpoint_url) -> tuple[bool, str]: if not parsed.port: return False, "No port specified in the URL" return True, "" + + +def retry_prompt( + helper_text: str, + rejection: Callable, + rejection_text: str, + default="", + show_default=False, + prompt_type=typer.prompt, +): + """ + Allows for asking prompts again if they do not meet a certain criteria (as defined in `rejection`) + Args: + helper_text: The helper text to display for the prompt + rejection: A function that returns True if the input should be rejected, and False if it should be accepted + rejection_text: The text to display to the user if their input hits the rejection + default: the default value to use for the prompt, default "" + show_default: whether to show the default, default False + prompt_type: the type of prompt, default `typer.prompt` + + Returns: the input value (or default) + + """ + while True: + var = prompt_type(helper_text, default=default, show_default=show_default) + if not rejection(var): + return var + else: + err_console.print(rejection_text) diff --git a/bittensor_cli/src/commands/root.py b/bittensor_cli/src/commands/root.py index c9d8f924..361b4f73 100644 --- a/bittensor_cli/src/commands/root.py +++ b/bittensor_cli/src/commands/root.py @@ -618,7 +618,7 @@ async def get_stake_for_coldkey_and_hotkey( f"\n[bold blue]Current stake[/bold blue]: [blue]{my_prev_delegated_stake}[/blue]\n" f"[bold white]Do you want to {delegate_string}:[/bold white]\n" f" [bold red]amount[/bold red]: [red]{staking_balance}\n[/red]" - f" [bold yellow]to hotkey[/bold yellow]: [yellow]{delegate_ss58}\n[/yellow]" + f" [bold yellow]{'to' if delegate_string == 'delegate' else 'from'} hotkey[/bold yellow]: [yellow]{delegate_ss58}\n[/yellow]" f" [bold green]hotkey owner[/bold green]: [green]{delegate_owner}[/green]" ): return False @@ -1749,10 +1749,8 @@ async def nominate(wallet: Wallet, subtensor: SubtensorInterface, prompt: bool): # Prompt use to set identity on chain. if prompt: - do_set_identity = Confirm.ask( - "Subnetwork registered successfully. Would you like to set your identity? [y/n]" - ) + do_set_identity = Confirm.ask("Would you like to set your identity? [y/n]") if do_set_identity: - id_prompts = set_id_prompts() + id_prompts = set_id_prompts(validator=True) await set_id(wallet, subtensor, *id_prompts, prompt=prompt) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index d156b00a..095c9441 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -14,7 +14,6 @@ from bittensor_cli.src.bittensor.balances import Balance -from bittensor_cli.src.bittensor.chain_data import decode_account_id from bittensor_cli.src.bittensor.utils import ( console, create_table, @@ -75,24 +74,6 @@ async def _check_threshold_amount( return True, min_req_stake -async def _get_hotkey_owner( - subtensor: "SubtensorInterface", hotkey_ss58: str, block_hash: str -) -> Optional[str]: - hk_owner_query = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="Owner", - params=[hotkey_ss58], - block_hash=block_hash, - ) - val = decode_account_id(hk_owner_query[0]) - if val: - exists = await subtensor.does_hotkey_exist(val, block_hash=block_hash) - else: - exists = False - hotkey_owner = val if exists else None - return hotkey_owner - - async def add_stake_extrinsic( subtensor: "SubtensorInterface", wallet: Wallet, @@ -141,8 +122,8 @@ async def add_stake_extrinsic( block_hash = await subtensor.substrate.get_chain_head() # Get hotkey owner print_verbose("Confirming hotkey owner", status) - hotkey_owner = await _get_hotkey_owner( - subtensor, hotkey_ss58=hotkey_ss58, block_hash=block_hash + hotkey_owner = await subtensor.get_hotkey_owner( + hotkey_ss58=hotkey_ss58, block_hash=block_hash ) own_hotkey = wallet.coldkeypub.ss58_address == hotkey_owner if not own_hotkey: @@ -532,7 +513,7 @@ async def unstake_extrinsic( hotkey_ss58=hotkey_ss58, block_hash=block_hash, ), - _get_hotkey_owner(subtensor, hotkey_ss58, block_hash), + subtensor.get_hotkey_owner(hotkey_ss58, block_hash), ) own_hotkey: bool = wallet.coldkeypub.ss58_address == hotkey_owner @@ -702,7 +683,7 @@ async def unstake_multiple_extrinsic( ] ) hotkey_owners_ = asyncio.gather( - *[_get_hotkey_owner(subtensor, h, block_hash) for h in hotkey_ss58s] + *[subtensor.get_hotkey_owner(h, block_hash) for h in hotkey_ss58s] ) old_balance, old_stakes, hotkey_owners, threshold = await asyncio.gather( diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index 8ae45ace..4440d034 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -384,7 +384,7 @@ async def create(wallet: Wallet, subtensor: "SubtensorInterface", prompt: bool): ) if do_set_identity: - id_prompts = set_id_prompts() + id_prompts = set_id_prompts(validator=False) await set_id(wallet, subtensor, *id_prompts, prompt=prompt) diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index b625530b..bf3f47e5 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -2,8 +2,10 @@ import binascii import itertools import os +import sys from collections import defaultdict from concurrent.futures import ProcessPoolExecutor +from functools import partial from sys import getsizeof from typing import Any, Collection, Generator, Optional @@ -18,6 +20,7 @@ from rich.table import Column, Table from rich.tree import Tree from rich.padding import Padding +from rich.prompt import IntPrompt from scalecodec import ScaleBytes import scalecodec import typer @@ -30,10 +33,12 @@ NeuronInfoLite, StakeInfo, custom_rpc_type_registry, + decode_account_id, ) from bittensor_cli.src.bittensor.extrinsics.registration import ( run_faucet_extrinsic, swap_hotkey_extrinsic, + is_hotkey_registered, ) from bittensor_cli.src.bittensor.extrinsics.transfer import transfer_extrinsic from bittensor_cli.src.bittensor.networking import int_to_ip @@ -50,6 +55,7 @@ get_hotkey_wallets_for_wallet, is_valid_ss58_address, validate_coldkey_presence, + retry_prompt, ) @@ -1433,28 +1439,46 @@ async def swap_hotkey( ) -def set_id_prompts() -> tuple[str, str, str, str, str, str, str, str, str, bool]: +def set_id_prompts( + validator: bool, +) -> tuple[str, str, str, str, str, str, str, str, str, bool, int]: """ Used to prompt the user to input their info for setting the ID :return: (display_name, legal_name, web_url, riot_handle, email,pgp_fingerprint, image_url, info_, twitter_url, validator_id) """ - display_name = Prompt.ask( - "Display Name: The display name for the identity.", default="" + text_rejection = partial( + retry_prompt, + rejection=lambda x: sys.getsizeof(x) > 113, + rejection_text="[red]Error:[/red] Identity field must be <= 64 raw bytes.", ) - legal_name = Prompt.ask("Legal Name: The legal name for the identity.", default="") - web_url = Prompt.ask("Web URL: The web url for the identity.", default="") - riot_handle = Prompt.ask( - "Riot Handle: The riot handle for the identity.", default="" - ) - email = Prompt.ask("Email: The email address for the identity.", default="") - pgp_fingerprint = Prompt.ask( - "PGP Fingerprint: The pgp fingerprint for the identity.", default="" + + def pgp_check(s: str): + try: + if s.startswith("0x"): + s = s[2:] # Strip '0x' + pgp_fingerprint_encoded = binascii.unhexlify(s.replace(" ", "")) + except Exception: + return True + return True if len(pgp_fingerprint_encoded) != 20 else False + + display_name = text_rejection("Display name") + legal_name = text_rejection("Legal name") + web_url = text_rejection("Web URL") + riot_handle = text_rejection("Riot handle") + email = text_rejection("Email address") + pgp_fingerprint = retry_prompt( + "PGP fingerprint (Eg: A1B2 C3D4 E5F6 7890 1234 5678 9ABC DEF0 1234 5678)", + lambda s: False if not s else pgp_check(s), + "[red]Error:[/red] PGP Fingerprint must be exactly 20 bytes.", ) - image_url = Prompt.ask("Image URL: The image url for the identity.", default="") - info_ = Prompt.ask("Info: Info about the identity", default="") - twitter_url = Prompt.ask("𝕏 URL: The 𝕏 (Twitter) url for the identity.") - validator_id = Confirm.ask("Are you updating a validator hotkey identity?") + image_url = text_rejection("Image URL") + info_ = text_rejection("Enter info") + twitter_url = text_rejection("𝕏 (Twitter) URL") + + subnet_netuid = None + if validator is False: + subnet_netuid = IntPrompt.ask("Enter the netuid of the subnet you own") return ( display_name, @@ -1466,7 +1490,8 @@ def set_id_prompts() -> tuple[str, str, str, str, str, str, str, str, str, bool] image_url, twitter_url, info_, - validator_id, + validator, + subnet_netuid, ) @@ -1483,22 +1508,17 @@ async def set_id( twitter: str, info_: str, validator_id: bool, + subnet_netuid: int, prompt: bool, ): """Create a new or update existing identity on-chain.""" - try: - pgp_fingerprint_encoded = binascii.unhexlify(pgp_fingerprint.replace(" ", "")) - except Exception as e: - print_error(f"The PGP is not in the correct format: {e}") - raise typer.Exit() - id_dict = { "additional": [[]], "display": display_name, "legal": legal_name, "web": web_url, - "pgp_fingerprint": pgp_fingerprint_encoded, + "pgp_fingerprint": pgp_fingerprint, "riot": riot_handle, "email": email, "image": image, @@ -1506,6 +1526,12 @@ async def set_id( "info": info_, } + try: + pgp_fingerprint_encoded = binascii.unhexlify(pgp_fingerprint.replace(" ", "")) + except Exception as e: + print_error(f"The PGP is not in the correct format: {e}") + raise typer.Exit() + for field, string in id_dict.items(): if ( field == "pgp_fingerprint" @@ -1550,6 +1576,37 @@ async def set_id( console.print(":cross_mark: Aborted!") raise typer.Exit() + if validator_id: + block_hash = await subtensor.substrate.get_chain_head() + + is_registered_on_root, hotkey_owner = await asyncio.gather( + is_hotkey_registered( + subtensor, netuid=0, hotkey_ss58=wallet.hotkey.ss58_address + ), + subtensor.get_hotkey_owner( + hotkey_ss58=wallet.hotkey.ss58_address, block_hash=block_hash + ), + ) + + if not is_registered_on_root: + print_error("The hotkey is not registered on root. Aborting.") + return False + + own_hotkey = wallet.coldkeypub.ss58_address == hotkey_owner + if not own_hotkey: + print_error("The hotkey doesn't belong to the coldkey wallet. Aborting.") + return False + else: + subnet_owner_ = await subtensor.substrate.query( + module="SubtensorModule", + storage_function="SubnetOwner", + params=[subnet_netuid], + ) + subnet_owner = decode_account_id(subnet_owner_[0]) + if subnet_owner != wallet.coldkeypub.ss58_address: + print_error(f":cross_mark: This wallet doesn't own subnet {subnet_netuid}.") + return False + try: wallet.unlock_coldkey() except KeyFileError: diff --git a/tests/e2e_tests/test_wallet_interactions.py b/tests/e2e_tests/test_wallet_interactions.py index a1055e78..fa17c3eb 100644 --- a/tests/e2e_tests/test_wallet_interactions.py +++ b/tests/e2e_tests/test_wallet_interactions.py @@ -354,6 +354,25 @@ 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", + 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 + # Define values for Alice's identity alice_identity = { "display_name": "Alice", @@ -398,7 +417,7 @@ def test_wallet_identities(local_chain, wallet_setup): alice_identity["info"], "-x", alice_identity["twitter"], - "--validator-id", + "--validator", "--no-prompt", ], ) @@ -481,4 +500,4 @@ def test_wallet_identities(local_chain, wallet_setup): assert "Message signed successfully" in sign_using_coldkey.stdout - print("✅Passed wallet set-id, get-id, sign command") + print("✅ Passed wallet set-id, get-id, sign command") From 53da63d9ea25e7e9a22a6cd0881483d443df9e05 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 2 Oct 2024 12:02:18 -0700 Subject: [PATCH 11/17] Updates changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f11abd..e234c0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 8.1.0 /2024-10-02 + +## What's Changed +* Allow for delegate take between 0 and 18% by @garrett-opentensor in https://github.com/opentensor/btcli/pull/123 +* Fixed: wallet balance check when undelegating the stake by @the-mx in https://github.com/opentensor/btcli/pull/124 +* `root my-delegates` ask for path instead of name when using `--all` by @thewhaleking in https://github.com/opentensor/btcli/pull/126 +* Fix/delegate all by @the-mx in https://github.com/opentensor/btcli/pull/125 +* Handle SSL errors and avoid unnecessary chain head calls by @thewhaleking in https://github.com/opentensor/btcli/pull/127 +* Deprecate: Remove chain config by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/128 +* Update staging by @thewhaleking in https://github.com/opentensor/btcli/pull/130 +* set archive node properly by @thewhaleking in https://github.com/opentensor/btcli/pull/143 +* Randomise rpc request ID by @thewhaleking in https://github.com/opentensor/btcli/pull/131 +* update help text in the BTCLI by @dougsillars in https://github.com/opentensor/btcli/pull/139 +* Backmerge/main to staging - 1st oct by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/145 +* Backmerge main to staging by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/147 +* Updates "btcli w set-identity" by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/146 + +## New Contributors +* @the-mx made their first contribution in https://github.com/opentensor/btcli/pull/124 +* @dougsillars made their first contribution in https://github.com/opentensor/btcli/pull/139 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v8.0.0...v8.1.0 + ## 8.0.0 /2024-09-25 ## What's Changed From d2c19682b60f9b73582bb70154ec16f0e11f6f85 Mon Sep 17 00:00:00 2001 From: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Date: Wed, 2 Oct 2024 22:21:21 +0200 Subject: [PATCH 12/17] Add current commit to version. (#156) --- bittensor_cli/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 45e6379c..488d1d21 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -362,9 +362,11 @@ def version_callback(value: bool): """ if value: try: + repo = Repo(os.path.dirname(os.path.dirname(__file__))) version = ( f"BTCLI version: {__version__}/" - f"{Repo(os.path.dirname(os.path.dirname(__file__))).active_branch.name}" + f"{repo.active_branch.name}/" + f"{repo.commit()}" ) except GitError: version = f"BTCLI version: {__version__}" From 1a3a9442b852f33d49d9250fe5e44d62ea9d470a Mon Sep 17 00:00:00 2001 From: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Date: Wed, 2 Oct 2024 22:22:42 +0200 Subject: [PATCH 13/17] Rename `not_subtensor` to `subtensor` (#157) --- bittensor_cli/cli.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 488d1d21..9b0b874c 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -383,10 +383,10 @@ class CLIManager: :var stake_app: the Typer app as it relates to stake commands :var sudo_app: the Typer app as it relates to sudo commands :var subnets_app: the Typer app as it relates to subnets commands - :var not_subtensor: the `SubtensorInterface` object passed to the various commands that require it + :var subtensor: the `SubtensorInterface` object passed to the various commands that require it """ - not_subtensor: Optional[SubtensorInterface] + subtensor: Optional[SubtensorInterface] app: typer.Typer config_app: typer.Typer wallet_app: typer.Typer @@ -419,7 +419,7 @@ def __init__(self): "COLDKEY": True, }, } - self.not_subtensor = None + self.subtensor = None self.config_base_path = os.path.expanduser(defaults.config.base_path) self.config_path = os.path.expanduser(defaults.config.path) @@ -743,22 +743,22 @@ def initialize_chain( ) -> SubtensorInterface: """ Intelligently initializes a connection to the chain, depending on the supplied (or in config) values. Sets the - `self.not_subtensor` object to this created connection. + `self.subtensor` object to this created connection. :param network: Network name (e.g. finney, test, etc.) or chain endpoint (e.g. ws://127.0.0.1:9945, wss://entrypoint-finney.opentensor.ai:443) """ - if not self.not_subtensor: + if not self.subtensor: if network: - self.not_subtensor = SubtensorInterface(network) + self.subtensor = SubtensorInterface(network) elif self.config["network"]: - self.not_subtensor = SubtensorInterface(self.config["network"]) + self.subtensor = SubtensorInterface(self.config["network"]) console.print( f"Using the specified network [dark_orange]{self.config['network']}[/dark_orange] from config" ) else: - self.not_subtensor = SubtensorInterface(defaults.subtensor.network) - return self.not_subtensor + self.subtensor = SubtensorInterface(defaults.subtensor.network) + return self.subtensor def _run_command(self, cmd: Coroutine) -> None: """ @@ -767,16 +767,14 @@ def _run_command(self, cmd: Coroutine) -> None: async def _run(): try: - if self.not_subtensor: - async with self.not_subtensor: + if self.subtensor: + async with self.subtensor: result = await cmd else: result = await cmd return result except (ConnectionRefusedError, ssl.SSLError): - err_console.print( - f"Unable to connect to the chain: {self.not_subtensor}" - ) + err_console.print(f"Unable to connect to the chain: {self.subtensor}") asyncio.create_task(cmd).cancel() raise typer.Exit() except ConnectionClosed: @@ -1431,7 +1429,7 @@ def wallet_swap_hotkey( ) self.initialize_chain(network) return self._run_command( - wallets.swap_hotkey(original_wallet, new_wallet, self.not_subtensor, prompt) + wallets.swap_hotkey(original_wallet, new_wallet, self.subtensor, prompt) ) def wallet_inspect( @@ -1501,7 +1499,7 @@ def wallet_inspect( return self._run_command( wallets.inspect( wallet, - self.not_subtensor, + self.subtensor, netuids_filter=netuids, all_wallets=all_wallets, ) @@ -1890,7 +1888,7 @@ def wallet_check_ck_swap( self.verbosity_handler(quiet, verbose) wallet = self.wallet_ask(wallet_name, wallet_path, wallet_hotkey) self.initialize_chain(network) - return self._run_command(wallets.check_coldkey_swap(wallet, self.not_subtensor)) + return self._run_command(wallets.check_coldkey_swap(wallet, self.subtensor)) def wallet_create_wallet( self, From a798d3bd54b691484172b5db266d159d557c9b83 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Wed, 2 Oct 2024 13:24:46 -0700 Subject: [PATCH 14/17] Changelog updated --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e234c0dd..7a267c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ * Backmerge/main to staging - 1st oct by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/145 * Backmerge main to staging by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/147 * Updates "btcli w set-identity" by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/146 +* Give recent commit in version by @thewhaleking in https://github.com/opentensor/btcli/pull/156 +* Rename `not_subtensor` to `subtensor` by @thewhaleking in https://github.com/opentensor/btcli/pull/157 ## New Contributors * @the-mx made their first contribution in https://github.com/opentensor/btcli/pull/124 From 1130b9ebbb520ea6d17bbdad193b55faf9c141a7 Mon Sep 17 00:00:00 2001 From: Benjamin Himes <37844818+thewhaleking@users.noreply.github.com> Date: Fri, 4 Oct 2024 03:30:00 +0200 Subject: [PATCH 15/17] =?UTF-8?q?Integrate=20Rust=20Wallet=20=E2=80=94=20t?= =?UTF-8?q?ests=20(#158)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added the `--help` message automatically when no command is specified, both for `btcli` itself, and all major commands (not subcommands). * Adding PyPI badge * Switch Keypair import to bittensor_wallet and update tests Replace the Keypair import from substrateinterface to bittensor_wallet across multiple files. Adjust the requirements to use bittensor-wallet version 2.0.0 and add slight adjustments to test timing with sleep intervals. * add tests dir init * use relative imports * change runner params and fix grab mne * assert output of exception and stdout first * Json tuple handling + ruff format. * change empty json handling * json for regen hotkey * Asserting error in stderr * lower wait time for mod check * Fixed wallet path by ensuring that we expand the user dir, working wallet list * Always ask for WALLET.PATH because the new Wallet doesn't handle None path the same as the old one. * Updated the helper text in wallet ask to let users know they can set these values in the config. * Fixed new error (TypeError) in btwallet 2.0 causing .DS_Store * Extend wallet_path fix to regen coldkey, coldkey pub + improve text layout of hints * Add a 10 second sleep, which seems to be long enough to avoid flakiness with the test. * Testing alpha release * Bumping wallet version for testing * Wallet -> 2.0.0 * Removes comments --------- Co-authored-by: Watchmaker Co-authored-by: opendansor Co-authored-by: Cameron Fairchild Co-authored-by: ibraheem-opentensor --- bittensor_cli/cli.py | 123 +++++++++++------- .../bittensor/async_substrate_interface.py | 2 +- .../src/bittensor/extrinsics/root.py | 2 +- .../src/bittensor/extrinsics/transfer.py | 6 +- bittensor_cli/src/bittensor/utils.py | 24 ++-- bittensor_cli/src/commands/wallets.py | 4 +- requirements.txt | 2 +- tests/__init__.py | 0 tests/e2e_tests/test_root.py | 5 +- tests/e2e_tests/test_wallet_creations.py | 35 +++-- tests/e2e_tests/test_wallet_interactions.py | 10 +- tests/e2e_tests/utils.py | 12 +- 12 files changed, 139 insertions(+), 86 deletions(-) create mode 100644 tests/__init__.py diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 9b0b874c..79fc418f 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1098,7 +1098,12 @@ def wallet_ask( ) else: wallet_name = typer.prompt( - typer.style("Enter the wallet name", fg="blue"), + typer.style("Enter the wallet name", fg="blue") + + typer.style( + " (Hint: You can set this with `btcli config set --wallet-name`)", + fg="green", + italic=True, + ), default=defaults.wallet.name, ) @@ -1110,7 +1115,12 @@ def wallet_ask( ) else: wallet_hotkey = typer.prompt( - typer.style("Enter the wallet hotkey", fg="blue"), + typer.style("Enter the wallet hotkey", fg="blue") + + typer.style( + " (Hint: You can set this with `btcli config set --wallet-hotkey`)", + fg="green", + italic=True, + ), default=defaults.wallet.hotkey, ) if wallet_path: @@ -1125,10 +1135,17 @@ def wallet_ask( if WO.PATH in ask_for and not wallet_path: wallet_path = typer.prompt( - typer.style("Enter the wallet path", fg="blue"), + typer.style("Enter the wallet path", fg="blue") + + typer.style( + " (Hint: You can set this with `btcli config set --wallet-path`)", + fg="green", + italic=True, + ), default=defaults.wallet.path, ) # Create the Wallet object + if wallet_path: + wallet_path = os.path.expanduser(wallet_path) wallet = Wallet(name=wallet_name, path=wallet_path, hotkey=wallet_hotkey) # Validate the wallet if required @@ -1136,13 +1153,15 @@ def wallet_ask( valid = utils.is_valid_wallet(wallet) if not valid[0]: utils.err_console.print( - f"[red]Error: Wallet does not not exist. \nPlease verify your wallet information: {wallet}[/red]" + f"[red]Error: Wallet does not not exist. \n" + f"Please verify your wallet information: {wallet}[/red]" ) raise typer.Exit() if validate == WV.WALLET_AND_HOTKEY and not valid[1]: utils.err_console.print( - f"[red]Error: Wallet '{wallet.name}' exists but the hotkey '{wallet.hotkey_str}' does not. \nPlease verify your wallet information: {wallet}[/red]" + f"[red]Error: Wallet '{wallet.name}' exists but the hotkey '{wallet.hotkey_str}' does not. \n" + f"Please verify your wallet information: {wallet}[/red]" ) raise typer.Exit() return wallet @@ -1282,7 +1301,7 @@ def wallet_overview( "Netuids must be a comma-separated list of ints, e.g., `--netuids 1,2,3,4`.", ) - ask_for = [WO.NAME] if not all_wallets else [WO.PATH] + ask_for = [WO.NAME, WO.PATH] if not all_wallets else [WO.PATH] validate = WV.WALLET if not all_wallets else WV.NONE wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate @@ -1367,7 +1386,7 @@ def wallet_transfer( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME], + ask_for=[WO.NAME, WO.PATH], validate=WV.WALLET, ) subtensor = self.initialize_chain(network) @@ -1412,7 +1431,7 @@ def wallet_swap_hotkey( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) if not destination_hotkey_name: @@ -1424,7 +1443,7 @@ def wallet_swap_hotkey( wallet_name, wallet_path, destination_hotkey_name, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) self.initialize_chain(network) @@ -1490,7 +1509,7 @@ def wallet_inspect( ) # if all-wallets is entered, ask for path - ask_for = [WO.NAME] if not all_wallets else [WO.PATH] + ask_for = [WO.NAME, WO.PATH] if not all_wallets else [WO.PATH] validate = WV.WALLET if not all_wallets else WV.NONE wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate @@ -1579,7 +1598,7 @@ def wallet_faucet( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME], + ask_for=[WO.NAME, WO.PATH], validate=WV.WALLET, ) return self._run_command( @@ -1632,6 +1651,7 @@ def wallet_regen_coldkey( wallet_path = Prompt.ask( "Enter the path for the wallets directory", default=defaults.wallet.path ) + wallet_path = os.path.expanduser(wallet_path) if not wallet_name: wallet_name = Prompt.ask( @@ -1685,6 +1705,7 @@ def wallet_regen_coldkey_pub( wallet_path = Prompt.ask( "Enter the path to the wallets directory", default=defaults.wallet.path ) + wallet_path = os.path.expanduser(wallet_path) if not wallet_name: wallet_name = Prompt.ask( @@ -1748,7 +1769,7 @@ def wallet_regen_hotkey( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET, ) mnemonic, seed, json, json_password = get_creation_data( @@ -1815,7 +1836,7 @@ def wallet_new_hotkey( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET, ) n_words = get_n_words(n_words) @@ -1861,7 +1882,13 @@ def wallet_new_coldkey( "Enter the name of the new wallet", default=defaults.wallet.name ) - wallet = Wallet(wallet_name, wallet_hotkey, wallet_path) + wallet = self.wallet_ask( + wallet_name, + wallet_path, + wallet_hotkey, + ask_for=[WO.NAME, WO.PATH], + validate=WV.NONE, + ) n_words = get_n_words(n_words) return self._run_command(wallets.new_coldkey(wallet, n_words, use_password)) @@ -1933,7 +1960,7 @@ def wallet_create_wallet( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.NONE, ) n_words = get_n_words(n_words) @@ -1979,7 +2006,7 @@ def wallet_balance( """ self.verbosity_handler(quiet, verbose) - ask_for = [WO.PATH] if all_balances else [WO.NAME] + ask_for = [WO.PATH] if all_balances else [WO.NAME, WO.PATH] validate = WV.NONE if all_balances else WV.WALLET wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=ask_for, validate=validate @@ -2021,7 +2048,7 @@ def wallet_history( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME], + ask_for=[WO.NAME, WO.PATH], validate=WV.WALLET, ) return self._run_command(wallets.wallet_history(wallet)) @@ -2122,7 +2149,7 @@ def wallet_set_id( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.HOTKEY, WO.NAME], + ask_for=[WO.HOTKEY, WO.PATH, WO.NAME], validate=WV.WALLET_AND_HOTKEY, ) @@ -2277,7 +2304,7 @@ def wallet_sign( default=False, ) - ask_for = [WO.HOTKEY, WO.NAME] if use_hotkey else [WO.NAME] + ask_for = [WO.HOTKEY, WO.PATH, WO.NAME] if use_hotkey else [WO.NAME, WO.PATH] validate = WV.WALLET_AND_HOTKEY if use_hotkey else WV.WALLET wallet = self.wallet_ask( @@ -2380,7 +2407,7 @@ def root_set_weights( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.HOTKEY, WO.NAME], + ask_for=[WO.HOTKEY, WO.PATH, WO.NAME], validate=WV.WALLET_AND_HOTKEY, ) self._run_command( @@ -2471,7 +2498,7 @@ def root_boost( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -2512,7 +2539,7 @@ def root_slash( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -2562,7 +2589,7 @@ def root_senate_vote( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -2617,7 +2644,7 @@ def root_register( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -2686,7 +2713,7 @@ def root_set_take( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) @@ -2757,7 +2784,7 @@ def root_delegate_stake( ) wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) return self._run_command( root.delegate_stake( @@ -2827,7 +2854,7 @@ def root_undelegate_stake( ) wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) self._run_command( root.delegate_unstake( @@ -2899,7 +2926,7 @@ def root_my_delegates( wallet_name, wallet_path, wallet_hotkey, - ask_for=([WO.NAME] if not all_wallets else [WO.PATH]), + ask_for=([WO.NAME, WO.PATH] if not all_wallets else [WO.PATH]), validate=WV.WALLET if not all_wallets else WV.NONE, ) self._run_command( @@ -3013,7 +3040,7 @@ def root_nominate( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -3083,7 +3110,7 @@ def stake_show( ) else: wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) return self._run_command( @@ -3196,7 +3223,7 @@ def stake_add( if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) else: wallet_hotkey = hotkey_or_ss58 @@ -3204,20 +3231,20 @@ def stake_add( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.HOTKEY, WO.PATH], validate=WV.WALLET_AND_HOTKEY, ) elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address: wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) else: wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) @@ -3350,7 +3377,7 @@ def stake_remove( if is_valid_ss58_address(hotkey_or_ss58): hotkey_ss58_address = hotkey_or_ss58 wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) else: wallet_hotkey = hotkey_or_ss58 @@ -3358,13 +3385,13 @@ def stake_remove( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) elif all_hotkeys or include_hotkeys or exclude_hotkeys or hotkey_ss58_address: wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) else: @@ -3372,7 +3399,7 @@ def stake_remove( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) @@ -3443,7 +3470,7 @@ def stake_get_children( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) @@ -3544,7 +3571,7 @@ def stake_set_children( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -3596,7 +3623,7 @@ def stake_revoke_children( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) if all_netuids and netuid: @@ -3670,7 +3697,7 @@ def stake_childkey_take( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) if all_netuids and netuid: @@ -3748,7 +3775,7 @@ def sudo_set( ) wallet = self.wallet_ask( - wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME] + wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) return self._run_command( sudo.sudo_set_hyperparameter( @@ -3867,7 +3894,7 @@ def subnets_create( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -3946,7 +3973,7 @@ def subnets_pow_register( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ), self.initialize_chain(network), @@ -3988,7 +4015,7 @@ def subnets_register( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( @@ -4175,7 +4202,7 @@ def weights_reveal( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) @@ -4271,7 +4298,7 @@ def weights_commit( wallet_name, wallet_path, wallet_hotkey, - ask_for=[WO.NAME, WO.HOTKEY], + ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) return self._run_command( diff --git a/bittensor_cli/src/bittensor/async_substrate_interface.py b/bittensor_cli/src/bittensor/async_substrate_interface.py index c38aca13..46463595 100644 --- a/bittensor_cli/src/bittensor/async_substrate_interface.py +++ b/bittensor_cli/src/bittensor/async_substrate_interface.py @@ -12,7 +12,7 @@ from scalecodec.base import ScaleBytes, ScaleType, RuntimeConfigurationObject from scalecodec.type_registry import load_type_registry_preset from scalecodec.types import GenericCall -from substrateinterface import Keypair +from bittensor_wallet import Keypair from substrateinterface.exceptions import ( SubstrateRequestException, ExtrinsicNotFound, diff --git a/bittensor_cli/src/bittensor/extrinsics/root.py b/bittensor_cli/src/bittensor/extrinsics/root.py index 16073950..712a70f0 100644 --- a/bittensor_cli/src/bittensor/extrinsics/root.py +++ b/bittensor_cli/src/bittensor/extrinsics/root.py @@ -27,8 +27,8 @@ from rich.prompt import Confirm from rich.table import Table, Column from scalecodec import ScaleBytes, U16, Vec -from substrateinterface import Keypair +from bittensor_wallet import Keypair from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface from bittensor_cli.src.bittensor.extrinsics.registration import is_hotkey_registered from bittensor_cli.src.bittensor.utils import ( diff --git a/bittensor_cli/src/bittensor/extrinsics/transfer.py b/bittensor_cli/src/bittensor/extrinsics/transfer.py index 060b1861..c5d3789f 100644 --- a/bittensor_cli/src/bittensor/extrinsics/transfer.py +++ b/bittensor_cli/src/bittensor/extrinsics/transfer.py @@ -121,10 +121,10 @@ async def do_transfer() -> tuple[bool, str, str]: print_verbose("Fetching existential and fee", status) block_hash = await subtensor.substrate.get_chain_head() account_balance_, existential_deposit = await asyncio.gather( - subtensor.get_balance(wallet.coldkey.ss58_address, block_hash=block_hash), + subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), subtensor.get_existential_deposit(block_hash=block_hash), ) - account_balance = account_balance_[wallet.coldkey.ss58_address] + account_balance = account_balance_[wallet.coldkeypub.ss58_address] fee = await get_transfer_fee() if not keep_alive: @@ -176,7 +176,7 @@ async def do_transfer() -> tuple[bool, str, str]: if success: with console.status(":satellite: Checking Balance...", spinner="aesthetic"): new_balance = await subtensor.get_balance( - wallet.coldkey.ss58_address, reuse_block=False + wallet.coldkeypub.ss58_address, reuse_block=False ) console.print( f"Balance:\n" diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index f42dcbdf..e981a11e 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -4,21 +4,24 @@ import webbrowser from pathlib import Path from typing import TYPE_CHECKING, Any, Collection, Optional, Union, Callable +from urllib.parse import urlparse -import numpy as np -import scalecodec -import typer -from bittensor_wallet import Wallet -from bittensor_wallet.keyfile import Keypair -from bittensor_wallet.utils import SS58_FORMAT, ss58 +from bittensor_wallet import Wallet, Keypair +from bittensor_wallet.utils import SS58_FORMAT +from bittensor_wallet import utils from jinja2 import Template from markupsafe import Markup +import numpy as np from numpy.typing import NDArray from rich.console import Console +import scalecodec from scalecodec.base import RuntimeConfiguration from scalecodec.type_registry import load_type_registry_preset +import typer + + from bittensor_cli.src.bittensor.balances import Balance -from urllib.parse import urlparse + if TYPE_CHECKING: from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters @@ -219,6 +222,7 @@ def get_hotkey_wallets_for_wallet( except ( UnicodeDecodeError, AttributeError, + TypeError ): # usually an unrelated file like .DS_Store continue @@ -302,10 +306,8 @@ def is_valid_ss58_address(address: str) -> bool: :return: `True` if the address is a valid ss58 address for Bittensor, `False` otherwise. """ try: - return ss58.is_valid_ss58_address( - address, valid_ss58_format=SS58_FORMAT - ) or ss58.is_valid_ss58_address( - address, valid_ss58_format=42 + return utils.is_valid_ss58_address( + address ) # Default substrate ss58 format (legacy) except IndexError: return False diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index bf3f47e5..dd91c199 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -78,7 +78,7 @@ async def regen_coldkey( wallet.regenerate_coldkey( mnemonic=mnemonic, seed=seed, - json=(json_str, json_password), + json=(json_str, json_password) if all([json_str, json_password]) else None, use_password=use_password, overwrite=False, ) @@ -123,7 +123,7 @@ async def regen_hotkey( wallet.regenerate_hotkey( mnemonic=mnemonic, seed=seed, - json=(json_str, json_password), + json=(json_str, json_password) if all([json_str, json_password]) else None, use_password=use_password, overwrite=False, ) diff --git a/requirements.txt b/requirements.txt index 7ec75c94..1a6550e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,5 +16,5 @@ scalecodec==1.2.11 substrate-interface~=1.7.9 typer~=0.12 websockets>=12.0 -bittensor-wallet==1.0.0 +bittensor-wallet==2.0.0 bt-decode==0.2.0a0 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/e2e_tests/test_root.py b/tests/e2e_tests/test_root.py index 26485c25..1e87dbfa 100644 --- a/tests/e2e_tests/test_root.py +++ b/tests/e2e_tests/test_root.py @@ -1,7 +1,7 @@ import time from bittensor_cli.src.bittensor.balances import Balance -from tests.e2e_tests.utils import extract_coldkey_balance +from .utils import extract_coldkey_balance """ Verify commands: @@ -217,10 +217,11 @@ def test_root_commands(local_chain, wallet_setup): "--delegate-ss58key", wallet_bob.hotkey.ss58_address, "--amount", - f"10", + "10", "--no-prompt", ], ) + time.sleep(10) assert "✅ Finalized" in undelegate_alice.stdout check_balance( diff --git a/tests/e2e_tests/test_wallet_creations.py b/tests/e2e_tests/test_wallet_creations.py index 80e0eb2c..23b1e18f 100644 --- a/tests/e2e_tests/test_wallet_creations.py +++ b/tests/e2e_tests/test_wallet_creations.py @@ -138,20 +138,28 @@ def extract_mnemonics_from_commands(output: str) -> Dict[str, Optional[str]]: dict: A dictionary keys 'coldkey' and 'hotkey', each containing their mnemonics. """ mnemonics: Dict[str, Optional[str]] = {"coldkey": None, "hotkey": None} - lines = output.splitlines() key_types = ["coldkey", "hotkey"] - command_prefix = "btcli w regen-" - for line in lines: - line = line.strip().lower() + # We will assume the most recent match is the one we want + for key_type in key_types: + # Note: python's re needs this P before the group name + # See: https://stackoverflow.com/questions/10059673/named-regular-expression-group-pgroup-nameregexp-what-does-p-stand-for + pat = re.compile(rf"(?P{key_type}).*?(?P(\w+( |)){{12}})") + matches = pat.search(output) + + groups = matches.groupdict() + + if len(groups.keys()) == 0: + mnemonics[key_type] = None + continue - if line.startswith(command_prefix): - for key_type in key_types: - if line.startswith(f"{command_prefix}{key_type} --mnemonic "): - mnemonic_phrase = line.split("--mnemonic ")[1].strip().strip('"') - mnemonics[key_type] = mnemonic_phrase - break + key_type_str = groups["key_type"] + if key_type != key_type_str: + continue + + mnemonic_phrase = groups["mnemonic"] + mnemonics[key_type] = mnemonic_phrase return mnemonics @@ -329,6 +337,11 @@ def test_wallet_regen(wallet_setup): ], ) + # Check for an exception first + assert result.exception is None + # Verify the command has output, as expected + assert result.stdout is not None + mnemonics = extract_mnemonics_from_commands(result.stdout) wallet_status, message = verify_wallet_dir( @@ -364,7 +377,7 @@ def test_wallet_regen(wallet_setup): ) # Wait a bit to ensure file system updates modification time - time.sleep(1) + time.sleep(0.01) new_coldkey_mod_time = os.path.getmtime(coldkey_path) diff --git a/tests/e2e_tests/test_wallet_interactions.py b/tests/e2e_tests/test_wallet_interactions.py index fa17c3eb..8641f369 100644 --- a/tests/e2e_tests/test_wallet_interactions.py +++ b/tests/e2e_tests/test_wallet_interactions.py @@ -1,5 +1,7 @@ +from time import sleep + from bittensor_cli.src.bittensor.balances import Balance -from tests.e2e_tests.utils import ( +from .utils import ( extract_coldkey_balance, validate_wallet_inspect, validate_wallet_overview, @@ -58,6 +60,8 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): ) assert f"✅ Registered subnetwork with netuid: {netuid}" in result.stdout + sleep(3) + # List all the subnets in the network subnets_list = exec_command( command="subnets", @@ -68,6 +72,8 @@ def test_wallet_overview_inspect(local_chain, wallet_setup): ], ) + sleep(3) + # Assert using regex that the subnet is visible in subnets list assert verify_subnet_entry(subnets_list.stdout, netuid, keypair.ss58_address) @@ -326,7 +332,7 @@ def test_wallet_transfer(local_chain, wallet_setup): ) # This transfer is expected to fail due to low balance - assert "❌ Not enough balance" in result.stdout + assert "❌ Not enough balance" in result.stderr print("✅Passed wallet transfer, balance command") diff --git a/tests/e2e_tests/utils.py b/tests/e2e_tests/utils.py index f5a70e70..9a339987 100644 --- a/tests/e2e_tests/utils.py +++ b/tests/e2e_tests/utils.py @@ -6,9 +6,8 @@ from typing import List, Tuple, TYPE_CHECKING from bittensor_cli.cli import CLIManager -from substrateinterface import Keypair +from bittensor_wallet import Keypair, Wallet from typer.testing import CliRunner -from bittensor_wallet import Wallet if TYPE_CHECKING: from bittensor_cli.src.bittensor.async_substrate_interface import ( @@ -34,7 +33,8 @@ def exec_command( inputs: List[str] = None, ): cli_manager = CLIManager() - runner = CliRunner() + # Capture stderr separately from stdout + runner = CliRunner(mix_stderr=False) # Prepare the command arguments args = [ command, @@ -48,7 +48,11 @@ def exec_command( input_text = "\n".join(inputs) + "\n" if inputs else None result = runner.invoke( - cli_manager.app, args, input=input_text, env={"COLUMNS": "700"} + cli_manager.app, + args, + input=input_text, + env={"COLUMNS": "700"}, + catch_exceptions=False, ) return result From 4756c337683fb4b638610376cc6c53cd7b6007e8 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 3 Oct 2024 18:34:01 -0700 Subject: [PATCH 16/17] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a267c49..9bbdb81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ * Updates "btcli w set-identity" by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/146 * Give recent commit in version by @thewhaleking in https://github.com/opentensor/btcli/pull/156 * Rename `not_subtensor` to `subtensor` by @thewhaleking in https://github.com/opentensor/btcli/pull/157 +* Integrate Rust Wallet — tests by @thewhaleking @opendansor @roman-opentensor @ibraheem-opentensor @camfairchild in https://github.com/opentensor/btcli/pull/158 ## New Contributors * @the-mx made their first contribution in https://github.com/opentensor/btcli/pull/124 From c23d0fa346f4acee9291dc2d3e2abe877c202323 Mon Sep 17 00:00:00 2001 From: ibraheem-opentensor Date: Thu, 3 Oct 2024 18:56:06 -0700 Subject: [PATCH 17/17] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bbdb81b..83184dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 8.1.0 /2024-10-02 +## 8.1.0 /2024-10-03 ## What's Changed * Allow for delegate take between 0 and 18% by @garrett-opentensor in https://github.com/opentensor/btcli/pull/123