From 2497c06dbda6017eb565d35fc849b74259aa972d Mon Sep 17 00:00:00 2001 From: bdhimes Date: Thu, 14 Aug 2025 17:49:05 +0200 Subject: [PATCH 01/12] Return --- bittensor_cli/src/commands/stake/remove.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 67f0109f..3a37b8cb 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -361,6 +361,7 @@ async def unstake( ) if json_output: json_console.print(json.dumps(successes)) + return True async def unstake_all( From fb10995dc1ff01be59f8f744477d5955fc4abeee Mon Sep 17 00:00:00 2001 From: bdhimes Date: Thu, 14 Aug 2025 22:26:28 +0200 Subject: [PATCH 02/12] Initial add for just the async-substrate-interface logger --- bittensor_cli/cli.py | 14 ++++++++++++++ bittensor_cli/src/__init__.py | 1 + pyproject.toml | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 6604c08b..694ed8ab 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -4,6 +4,7 @@ import curses import importlib import json +import logging import os.path import re import ssl @@ -665,6 +666,9 @@ def __init__(self): self.config_path = os.getenv("BTCLI_CONFIG_PATH") or os.path.expanduser( defaults.config.path ) + self.debug_file_path = os.getenv("BTCLI_DEBUG_FILE") or os.path.expanduser( + defaults.config.debug_file_path + ) self.app = typer.Typer( rich_markup_mode="rich", @@ -1221,6 +1225,16 @@ def main_callback( for k, v in config.items(): if k in self.config.keys(): self.config[k] = v + if self.config.get("use_cache", False): + with open(self.debug_file_path, "w+") as f: + f.write(f"BTCLI {__version__}\nCommand: {' '.join(sys.argv)}\n\n") + asi_logger = logging.getLogger("async_substrate_interface") + asi_logger.setLevel(logging.DEBUG) + formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s') + handler = logging.FileHandler(self.debug_file_path) + handler.setFormatter(formatter) + asi_logger.addHandler(handler) + def verbosity_handler( self, quiet: bool, verbose: bool, json_output: bool = False diff --git a/bittensor_cli/src/__init__.py b/bittensor_cli/src/__init__.py index afabc424..ef9493ca 100644 --- a/bittensor_cli/src/__init__.py +++ b/bittensor_cli/src/__init__.py @@ -88,6 +88,7 @@ class Defaults: class config: base_path = "~/.bittensor" path = "~/.bittensor/config.yml" + debug_file_path = "~/.bittensor/debug.txt" dictionary = { "network": None, "wallet_path": None, diff --git a/pyproject.toml b/pyproject.toml index 767047b3..5a47f2b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ scripts = { btcli = "bittensor_cli.cli:main" } requires-python = ">=3.9,<3.14" dependencies = [ "wheel", - "async-substrate-interface>=1.4.2", + "async-substrate-interface>=1.5.2", "aiohttp~=3.10.2", "backoff~=2.2.1", "GitPython>=3.0.0", From bd4de55c34ef9a5358610a3584cf77213132c8eb Mon Sep 17 00:00:00 2001 From: bdhimes Date: Thu, 14 Aug 2025 23:00:09 +0200 Subject: [PATCH 03/12] WIP check-in --- bittensor_cli/cli.py | 144 ++++++++++++++++++++++---- bittensor_cli/src/commands/wallets.py | 2 +- 2 files changed, 122 insertions(+), 24 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 694ed8ab..71ea58ea 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -87,6 +87,7 @@ class GitError(Exception): pass +logger = logging.getLogger("btcli") _epilog = "Made with [bold red]:heart:[/bold red] by The Openτensor Foundaτion" np.set_printoptions(precision=8, suppress=True, floatmode="fixed") @@ -1227,14 +1228,19 @@ def main_callback( self.config[k] = v if self.config.get("use_cache", False): with open(self.debug_file_path, "w+") as f: - f.write(f"BTCLI {__version__}\nCommand: {' '.join(sys.argv)}\n\n") + f.write( + f"BTCLI {__version__}\nCommand: {' '.join(sys.argv)}\n{self.config}\n\n" + ) asi_logger = logging.getLogger("async_substrate_interface") asi_logger.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s') + logger.setLevel(logging.DEBUG) + formatter = logging.Formatter( + "%(asctime)s - %(levelname)s - %(name)s - %(module)s:%(lineno)d - %(message)s" + ) handler = logging.FileHandler(self.debug_file_path) handler.setFormatter(formatter) asi_logger.addHandler(handler) - + logger.addHandler(handler) def verbosity_handler( self, quiet: bool, verbose: bool, json_output: bool = False @@ -1436,6 +1442,7 @@ def set_config( for arg, val in args.items(): if val is not None: + logger.debug(f"Config: setting {arg} to {val}") self.config[arg] = val with open(self.config_path, "w") as f: safe_dump(self.config, f) @@ -1503,6 +1510,7 @@ def del_config( if Confirm.ask( f"Do you want to clear the [{COLORS.G.ARG}]{arg}[/{COLORS.G.ARG}] config?" ): + logger.debug(f"Config: clearing {arg}.") self.config[arg] = None console.print( f"Cleared [{COLORS.G.ARG}]{arg}[/{COLORS.G.ARG}] config and set to 'None'." @@ -1522,6 +1530,7 @@ def del_config( f" [bold cyan]({self.config.get(arg)})[/bold cyan] config?" ): self.config[arg] = None + logger.debug(f"Config: clearing {arg}.") console.print( f"Cleared [{COLORS.G.ARG}]{arg}[/{COLORS.G.ARG}] config and set to 'None'." ) @@ -1623,26 +1632,31 @@ def ask_safe_staking( bool: Safe staking setting """ if safe_staking is not None: + enabled = "enabled" if safe_staking else "disabled" console.print( - f"[dim][blue]Safe staking[/blue]: [bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan]." + f"[dim][blue]Safe staking[/blue]: [bold cyan]{enabled}[/bold cyan]." ) + logger.debug(f"Safe staking {enabled}") return safe_staking elif self.config.get("safe_staking") is not None: safe_staking = self.config["safe_staking"] + enabled = "enabled" if safe_staking else "disabled" console.print( - f"[dim][blue]Safe staking[/blue]: [bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan] (from config)." + f"[dim][blue]Safe staking[/blue]: [bold cyan]{enabled}[/bold cyan] (from config)." ) + logger.debug(f"Safe staking {enabled}") return safe_staking else: safe_staking = True console.print( "[dim][blue]Safe staking[/blue]: " - + f"[bold cyan]{'enabled' if safe_staking else 'disabled'}[/bold cyan] " - + "by default. Set this using " - + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] " - + "or " - + "[dark_sea_green3 italic]`--safe/--unsafe`[/dark_sea_green3 italic] flag[/dim]" + f"[bold cyan]enabled[/bold cyan] " + "by default. Set this using " + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] " + "or " + "[dark_sea_green3 italic]`--safe/--unsafe`[/dark_sea_green3 italic] flag[/dim]" ) + logger.debug(f"Safe staking enabled.") return safe_staking def ask_partial_stake( @@ -1659,25 +1673,31 @@ def ask_partial_stake( bool: Partial stake setting """ if allow_partial_stake is not None: + partial_staking = "enabled" if allow_partial_stake else "disabled" console.print( - f"[dim][blue]Partial staking[/blue]: [bold cyan]{'enabled' if allow_partial_stake else 'disabled'}[/bold cyan]." + f"[dim][blue]Partial staking[/blue]: [bold cyan]{partial_staking}[/bold cyan]." ) + logger.debug(f"Partial staking {partial_staking}") return allow_partial_stake elif self.config.get("allow_partial_stake") is not None: config_partial = self.config["allow_partial_stake"] + partial_staking = "enabled" if allow_partial_stake else "disabled" console.print( - f"[dim][blue]Partial staking[/blue]: [bold cyan]{'enabled' if config_partial else 'disabled'}[/bold cyan] (from config)." + f"[dim][blue]Partial staking[/blue]: [bold cyan]{partial_staking}[/bold cyan] (from config)." ) + logger.debug(f"Partial staking {partial_staking}") return config_partial else: + partial_staking = "enabled" if allow_partial_stake else "disabled" console.print( "[dim][blue]Partial staking[/blue]: " - + f"[bold cyan]{'enabled' if allow_partial_stake else 'disabled'}[/bold cyan] " - + "by default. Set this using " - + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] " - + "or " - + "[dark_sea_green3 italic]`--partial/--no-partial`[/dark_sea_green3 italic] flag[/dim]" + f"[bold cyan]{partial_staking}[/bold cyan] " + "by default. Set this using " + "[dark_sea_green3 italic]`btcli config set`[/dark_sea_green3 italic] " + "or " + "[dark_sea_green3 italic]`--partial/--no-partial`[/dark_sea_green3 italic] flag[/dim]" ) + logger.debug(f"Partial staking {partial_staking}") return False def wallet_ask( @@ -1750,6 +1770,7 @@ def wallet_ask( if wallet_path: wallet_path = os.path.expanduser(wallet_path) wallet = Wallet(name=wallet_name, path=wallet_path, hotkey=wallet_hotkey) + logger.debug(f"Using wallet {wallet}") # Validate the wallet if required if validate == WV.WALLET or validate == WV.WALLET_AND_HOTKEY: @@ -1905,6 +1926,14 @@ def wallet_overview( str, "Hotkeys names must be a comma-separated list, e.g., `--exclude-hotkeys hk1,hk2`.", ) + logger.debug( + f"all_wallets: {all_wallets}\n" + f"sort_by: {sort_by}\n" + f"sort_order: {sort_order}\n" + f"include_hotkeys: {include_hotkeys}\n" + f"exclude_hotkeys: {exclude_hotkeys}\n" + f"netuids: {netuids}\n" + ) return self._run_command( wallets.overview( @@ -1994,6 +2023,14 @@ def wallet_transfer( amount = 0 elif not amount: amount = FloatPrompt.ask("Enter amount (in TAO) to transfer.") + logger.debug( + f"destination: {destination_ss58_address}\n" + f"amount: {amount}\n" + f"transfer_all: {transfer_all}\n" + f"allow_death: {allow_death}\n" + f"period: {period}\n" + f"prompt: {prompt}\n" + ) return self._run_command( wallets.transfer( wallet=wallet, @@ -2063,6 +2100,12 @@ def wallet_swap_hotkey( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) + logger.debug( + f"original_wallet: {original_wallet}\n" + f"new_wallet: {new_wallet}\n" + f"netuid: {netuid}\n" + f"prompt: {prompt}\n" + ) self.initialize_chain(network) return self._run_command( wallets.swap_hotkey( @@ -2226,6 +2269,16 @@ def wallet_faucet( ask_for=[WO.NAME, WO.PATH], validate=WV.WALLET, ) + logger.debug( + f"network {network}\n" + f"threads_per_block {threads_per_block}\n" + f"update_interval {update_interval}\n" + f"processors {processors}\n" + f"use_cuda {use_cuda}\n" + f"dev_id {dev_id}\n" + f"output_in_place {output_in_place}\n" + f"max_successes {max_successes}\n" + ) return self._run_command( wallets.faucet( wallet, @@ -2293,6 +2346,7 @@ def wallet_regen_coldkey( mnemonic, seed, json_path, json_password = get_creation_data( mnemonic, seed, json_path, json_password ) + # logger.debug should NOT be used here, it's simply too risky return self._run_command( wallets.regen_coldkey( wallet, @@ -2362,6 +2416,7 @@ def wallet_regen_coldkey_pub( ): rich.print("[red]Error: Invalid SS58 address or public key![/red]") return + # do not logger.debug any creation cmds return self._run_command( wallets.regen_coldkey_pub( wallet, ss58_address, public_key_hex, overwrite, json_output @@ -2414,6 +2469,7 @@ def wallet_regen_hotkey( mnemonic, seed, json_path, json_password = get_creation_data( mnemonic, seed, json_path, json_password ) + # do not logger.debug any creation cmds return self._run_command( wallets.regen_hotkey( wallet, @@ -2483,6 +2539,7 @@ def wallet_regen_hotkey_pub( ): rich.print("[red]Error: Invalid SS58 address or public key![/red]") return False + # do not logger.debug any creation cmds return self._run_command( wallets.regen_hotkey_pub( wallet, ss58_address, public_key_hex, overwrite, json_output @@ -2547,6 +2604,7 @@ def wallet_new_hotkey( ) if not uri: n_words = get_n_words(n_words) + # do not logger.debug any creation cmds return self._run_command( wallets.new_hotkey( wallet, n_words, use_password, uri, overwrite, json_output @@ -2613,7 +2671,12 @@ def wallet_associate_hotkey( f"hotkey [blue]{wallet_hotkey}[/blue] " f"[{COLORS.GENERAL.HK}]({hotkey_ss58})[/{COLORS.GENERAL.HK}]" ) - + logger.debug( + f"network {network}\n" + f"hotkey_ss58 {hotkey_ss58}\n" + f"hotkey_display {hotkey_display}\n" + f"prompt {prompt}\n" + ) return self._run_command( wallets.associate_hotkey( wallet, @@ -2761,7 +2824,8 @@ def wallet_check_ck_swap( if not scheduled_block: block_input = Prompt.ask( - "[blue]Enter the block number[/blue] where the swap was scheduled [dim](optional, press enter to skip)[/dim]", + "[blue]Enter the block number[/blue] where the swap was scheduled " + "[dim](optional, press enter to skip)[/dim]", default="", ) if block_input: @@ -2770,7 +2834,11 @@ def wallet_check_ck_swap( except ValueError: print_error("Invalid block number") raise typer.Exit() - + logger.debug( + f"scheduled_block {scheduled_block}\n" + f"ss58_address {ss58_address}\n" + f"network {network}\n" + ) return self._run_command( wallets.check_swap_status(self.subtensor, ss58_address, scheduled_block) ) @@ -2827,6 +2895,7 @@ def wallet_create_wallet( ) if not uri: n_words = get_n_words(n_words) + # do not logger.debug any creation commands return self._run_command( wallets.wallet_create( wallet, n_words, use_password, uri, overwrite, json_output @@ -2935,6 +3004,11 @@ def wallet_balance( ask_for=ask_for, validate=validate, ) + logger.debug( + f"all_balances {all_balances}\n" + f"ss58_addresses {ss58_addresses}\n" + f"network {network}" + ) subtensor = self.initialize_chain(network) return self._run_command( wallets.wallet_balance( @@ -3085,6 +3159,7 @@ def wallet_set_id( additional, github_repo, ) + logger.debug(f"identity {identity}\nnetwork {network}\n") return self._run_command( wallets.set_id( @@ -3346,7 +3421,11 @@ def wallet_swap_coldkey( f"[dark_sea_green3]{new_wallet}[/dark_sea_green3]\n" ) new_wallet_coldkey_ss58 = new_wallet.coldkeypub.ss58_address - + logger.debug( + f"network {network}\n" + f"new_coldkey_ss58 {new_wallet_coldkey_ss58}\n" + f"force_swap {force_swap}" + ) return self._run_command( wallets.schedule_coldkey_swap( wallet=wallet, @@ -3418,7 +3497,12 @@ def stake_list( wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) - + logger.debug( + f"coldkey_ss58 {coldkey_ss58}\n" + f"network {network}\n" + f"live: {live}\n" + f"no_prompt: {no_prompt}\n" + ) return self._run_command( list_stake.stake_list( wallet, @@ -3667,6 +3751,7 @@ def stake_add( ), exit_early=False, ) + logger.debug(f"Free balance: {free_balance}") if free_balance == Balance.from_tao(0): print_error("You dont have any balance to stake.") return @@ -3687,7 +3772,20 @@ def stake_add( f"You dont have enough balance to stake. Current free Balance: {free_balance}." ) raise typer.Exit() - + logger.debug( + f"network: {network}\n" + f"netuids: {netuids}\n" + f"stake_all: {stake_all}\n" + f"amount: {amount}\n" + f"prompt: {prompt}\n" + f"all_hotkeys: {all_hotkeys}\n" + f"include_hotkeys: {include_hotkeys}\n" + f"exclude_hotkeys: {exclude_hotkeys}\n" + f"safe_staking: {safe_staking}\n" + f"rate_tolerance: {rate_tolerance}\n" + f"allow_partial_stake: {allow_partial_stake}\n" + f"period: {period}\n" + ) return self._run_command( add_stake.stake_add( wallet, diff --git a/bittensor_cli/src/commands/wallets.py b/bittensor_cli/src/commands/wallets.py index ff37a50e..b45a5d0e 100644 --- a/bittensor_cli/src/commands/wallets.py +++ b/bittensor_cli/src/commands/wallets.py @@ -1836,7 +1836,7 @@ async def check_coldkey_swap(wallet: Wallet, subtensor: SubtensorInterface): async def sign( - wallet: Wallet, message: str, use_hotkey: str, json_output: bool = False + wallet: Wallet, message: str, use_hotkey: bool, json_output: bool = False ): """Sign a message using the provided wallet or hotkey.""" From ddd67668a7fd7e11aa31593afff9fa6aee68afc5 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Thu, 14 Aug 2025 23:31:08 +0200 Subject: [PATCH 04/12] Added most things --- bittensor_cli/cli.py | 122 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 8 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 71ea58ea..a4eefb62 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -4035,6 +4035,16 @@ def stake_remove( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) + logger.debug( + f"network: {network}\n" + f"hotkey_ss58_address: {hotkey_ss58_address}\n" + f"unstake_all: {unstake_all}\n" + f"unstake_all_alpha: {unstake_all_alpha}\n" + f"all_hotkeys: {all_hotkeys}\n" + f"include_hotkeys: {include_hotkeys}\n" + f"exclude_hotkeys: {exclude_hotkeys}\n" + f"era: {period}" + ) return self._run_command( remove_stake.unstake_all( wallet=wallet, @@ -4085,7 +4095,21 @@ def stake_remove( "Hotkeys must be a comma-separated list of ss58s or names, e.g., `--exclude-hotkeys hk3,hk4`.", is_ss58=False, ) - + logger.debug( + f"network: {network}\n" + f"hotkey_ss58_address: {hotkey_ss58_address}\n" + f"all_hotkeys: {all_hotkeys}\n" + f"include_hotkeys: {include_hotkeys}\n" + f"exclude_hotkeys: {exclude_hotkeys}\n" + f"amount: {amount}\n" + f"prompt: {prompt}\n" + f"interactive: {interactive}\n" + f"netuid: {netuid}\n" + f"safe_staking: {safe_staking}\n" + f"rate_tolerance: {rate_tolerance}\n" + f"allow_partial_stake: {allow_partial_stake}\n" + f"era: {period}" + ) return self._run_command( remove_stake.unstake( wallet=wallet, @@ -4249,7 +4273,18 @@ def stake_move( destination_netuid = IntPrompt.ask( "Enter the [blue]destination subnet[/blue] (netuid) to move stake to" ) - + logger.debug( + f"network: {network}\n" + f"origin_netuid: {origin_netuid}\n" + f"origin_hotkey: {origin_hotkey}\n" + f"destination_hotkey: {destination_hotkey}\n" + f"destination_netuid: {destination_netuid}\n" + f"amount: {amount}\n" + f"stake_all: {stake_all}\n" + f"era: {period}\n" + f"interactive_selection: {interactive_selection}\n" + f"prompt: {prompt}\n" + ) result = self._run_command( move_stake.move_stake( subtensor=self.initialize_chain(network), @@ -4414,7 +4449,17 @@ def stake_transfer( dest_netuid = IntPrompt.ask( "Enter the [blue]destination subnet[/blue] (netuid)" ) - + logger.debug( + f"network: {network}\n" + f"origin_hotkey: {origin_hotkey}\n" + f"origin_netuid: {origin_netuid}\n" + f"dest_netuid: {dest_netuid}\n" + f"dest_hotkey: {origin_hotkey}\n" + f"dest_coldkey_ss58: {dest_ss58}\n" + f"amount: {amount}\n" + f"era: {period}\n" + f"stake_all: {stake_all}" + ) result = self._run_command( move_stake.transfer_stake( wallet=wallet, @@ -4522,7 +4567,18 @@ def stake_swap( ) if not amount and not swap_all: amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to swap") - + logger.debug( + f"network: {network}\n" + f"origin_netuid: {origin_netuid}\n" + f"dest_netuid: {dest_netuid}\n" + f"amount: {amount}\n" + f"swap_all: {swap_all}\n" + f"era: {period}\n" + f"interactive_selection: {interactive_selection}\n" + f"prompt: {prompt}\n" + f"wait_for_inclusion: {wait_for_inclusion}\n" + f"wait_for_finalization: {wait_for_finalization}\n" + ) result = self._run_command( move_stake.swap_stake( wallet=wallet, @@ -4671,6 +4727,14 @@ def stake_set_children( ask_for=[WO.NAME, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) + logger.debug( + f"network: {network}\n" + f"netuid: {netuid}\n" + f"children: {children}\n" + f"proportions: {proportions}\n" + f"wait_for_inclusion: {wait_for_inclusion}\n" + f"wait_for_finalization: {wait_for_finalization}\n" + ) return self._run_command( children_hotkeys.set_children( wallet=wallet, @@ -4736,6 +4800,12 @@ def stake_revoke_children( netuid = IntPrompt.ask( "Enter netuid (leave blank for all)", default=None, show_default=True ) + logger.debug( + f"network: {network}\n" + f"netuid: {netuid}\n" + f"wait_for_inclusion: {wait_for_inclusion}\n" + f"wait_for_finalization: {wait_for_finalization}\n" + ) return self._run_command( children_hotkeys.revoke_children( wallet, @@ -4814,6 +4884,13 @@ def stake_childkey_take( netuid = IntPrompt.ask( "Enter netuid (leave blank for all)", default=None, show_default=True ) + logger.debug( + f"network: {network}\n" + f"netuid: {netuid}\n" + f"take: {take}\n" + f"wait_for_inclusion: {wait_for_inclusion}\n" + f"wait_for_finalization: {wait_for_finalization}\n" + ) results: list[tuple[Optional[int], bool]] = self._run_command( children_hotkeys.childkey_take( wallet=wallet, @@ -4949,6 +5026,12 @@ def sudo_set( wallet = self.wallet_ask( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) + logger.debug( + f"network: {network}\n" + f"netuid: {netuid}\n" + f"param_name: {param_name}\n" + f"param_value: {param_value}" + ) result, err_msg = self._run_command( sudo.sudo_set_hyperparameter( wallet, @@ -5069,6 +5152,7 @@ def sudo_senate_vote( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) + logger.debug(f"network: {network}\nproposal: {proposal}\nvote: {vote}\n") return self._run_command( sudo.senate_vote( wallet, self.initialize_chain(network), proposal, vote, prompt @@ -5121,7 +5205,7 @@ def sudo_set_take( f"Take value must be between {min_value} and {max_value}. Provided value: {take}" ) raise typer.Exit() - + logger.debug(f"network: {network}\ntake: {take}") result = self._run_command( sudo.set_take(wallet, self.initialize_chain(network), take) ) @@ -5465,6 +5549,7 @@ def subnets_create( logo_url=logo_url, additional=additional_info, ) + logger.debug(f"network: {network}\nidentity: {identity}\n") self._run_command( subnets.create( wallet, self.initialize_chain(network), identity, json_output, prompt @@ -5526,6 +5611,7 @@ def subnets_start( ], validate=WV.WALLET, ) + logger.debug(f"network: {network}\nnetuid: {netuid}\n") return self._run_command( subnets.start_subnet( wallet, @@ -5641,7 +5727,7 @@ def subnets_set_identity( logo_url=logo_url, additional=additional_info, ) - + logger.debug(f"network: {network}\nnetuid: {netuid}\nidentity: {identity}") success = self._run_command( subnets.set_identity( wallet, self.initialize_chain(network), netuid, identity, prompt @@ -5779,6 +5865,7 @@ def subnets_register( ask_for=[WO.NAME, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) + logger.debug(f"network: {network}\nnetuid: {netuid}\nperiod: {period}\n") return self._run_command( subnets.register( wallet, @@ -5976,7 +6063,6 @@ def weights_reveal( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) - return self._run_command( weights_cmds.reveal_weights( self.initialize_chain(network), @@ -6222,7 +6308,13 @@ def liquidity_add( if price_low >= price_high: err_console.print("The low price must be lower than the high price.") return False - + logger.debug( + f"hotkey: {hotkey}\n" + f"netuid: {netuid}\n" + f"liquidity: {liquidity_}\n" + f"price_low: {price_low}\n" + f"price_high: {price_high}\n" + ) return self._run_command( liquidity.add_liquidity( subtensor=self.initialize_chain(network), @@ -6323,6 +6415,13 @@ def liquidity_remove( validate=WV.WALLET, return_wallet_and_hotkey=True, ) + logger.debug( + f"network: {network}\n" + f"hotkey: {hotkey}\n" + f"netuid: {netuid}\n" + f"position_id: {position_id}\n" + f"all_liquidity_ids: {all_liquidity_ids}\n" + ) return self._run_command( liquidity.remove_liquidity( subtensor=self.initialize_chain(network), @@ -6387,6 +6486,13 @@ def liquidity_modify( f"[blue]{position_id}[/blue] (can be positive or negative)", negative_allowed=True, ) + logger.debug( + f"network: {network}\n" + f"hotkey: {hotkey}\n" + f"netuid: {netuid}\n" + f"position_id: {position_id}\n" + f"liquidity_delta: {liquidity_delta}" + ) return self._run_command( liquidity.modify_liquidity( From 27322279c7aaa453cdab3c8aa9805c840e44fd98 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Thu, 14 Aug 2025 23:45:53 +0200 Subject: [PATCH 05/12] Added `--debug` --- bittensor_cli/cli.py | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index a4eefb62..4cafc10d 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -594,7 +594,29 @@ def commands_callback(value: bool): if value: cli = CLIManager() console.print(cli.generate_command_tree()) - raise typer.Exit() + + +def debug_callback(value: bool): + if value: + save_file_loc_ = Prompt.ask( + "Enter the file location to save the debug log for the previous command.", + default="~/.bittensor/debug-export", + ).strip() + save_file_loc = os.path.expanduser(save_file_loc_) + try: + with ( + open(save_file_loc, "w+") as save_file, + open( + os.getenv("BTCLI_DEBUG_FILE") + or os.path.expanduser(defaults.config.debug_file_path), + "r", + ) as current_file, + ): + save_file.write(current_file.read()) + console.print(f"Saved debug log to {save_file_loc}") + except FileNotFoundError: + print_error(f"The filepath '{save_file_loc}' does not exist.") + raise typer.Exit() class CLIManager: @@ -1192,6 +1214,14 @@ def main_callback( "--commands", callback=commands_callback, help="Show BTCLI commands" ), ] = None, + debug_log: Annotated[ + Optional[bool], + typer.Option( + "--debug", + callback=debug_callback, + help="Saves the debug log from the last used command", + ), + ] = None, ): """ Command line interface (CLI) for Bittensor. Uses the values in the configuration file. These values can be From 9dfa055cb1fcf675df96f11016e3822f8e54e2cd Mon Sep 17 00:00:00 2001 From: bdhimes Date: Thu, 14 Aug 2025 23:52:54 +0200 Subject: [PATCH 06/12] ruff --- bittensor_cli/cli.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index ea8df234..c507f237 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -4160,7 +4160,6 @@ def stake_remove( f"era: {period}" ) - return self._run_command( remove_stake.unstake( wallet=wallet, From 2960cdeda8ea0a01c99c06a199948c3663116fa4 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 15 Aug 2025 00:22:57 +0200 Subject: [PATCH 07/12] Expand README --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index f9ea6cec..6d8b6abf 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,21 @@ BTCLI accepts a few environment variables that can alter how it works: - DISK_CACHE (default 0, also settable in config): If set to 1 (or set in config), will use disk caching for various safe-cachable substrate calls (such as block number to block hash mapping), which can speed up subsequent calls. - BTCLI_CONFIG_PATH (default `~/.bittensor/config.yml`): This will set the config file location, creating if it does not exist. + - BTCLI_DEBUG_FILE (default `~/.bittensor/debug.txt`): The file stores the most recent's command's debug log. + +--- + +## Debugging +BTCLI will store a debug log for every command run. This file is overwritten for each new command run. The default location +of this file is `~/.bittensor/debug.txt` and can be set with the `BTCLI_DEBUG_FILE` env var (see above section). + +The debug log will **NOT** contain any sensitive data (private keys), and is intended to be sent to the developers +for debugging. This file contains basic information about the command being run, the config, and the back and forth of requests and responses +to and from the chain. + +If you encounter an issue, and would like to save the file somewhere it won't be overwritten, run `btcli --debug`, +and set the save file location. We recommend doing this first before anything, and then starting your debugging with +us on our [Discord](https://discord.gg/bittensor). --- From c82f079b2eaeb8865ccfec33434cc39165179d8e Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 15 Aug 2025 13:53:42 +0200 Subject: [PATCH 08/12] Missed indent --- bittensor_cli/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index c507f237..a9a8a61e 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -616,7 +616,7 @@ def debug_callback(value: bool): console.print(f"Saved debug log to {save_file_loc}") except FileNotFoundError: print_error(f"The filepath '{save_file_loc}' does not exist.") - raise typer.Exit() + raise typer.Exit() class CLIManager: From 750221db7373ef58856195a0148c2a8ed3cc3fb4 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 15 Aug 2025 13:56:40 +0200 Subject: [PATCH 09/12] Add info about async substrate interface and bt-wallet --- bittensor_cli/cli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index a9a8a61e..41b163a5 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1259,7 +1259,11 @@ def main_callback( if self.config.get("use_cache", False): with open(self.debug_file_path, "w+") as f: f.write( - f"BTCLI {__version__}\nCommand: {' '.join(sys.argv)}\n{self.config}\n\n" + f"BTCLI {__version__}\n" + f"Async-Substrate-Interface: {importlib.metadata.version('async-substrate-interface')}\n" + f"Bittensor-Wallet: {importlib.metadata.version('bittensor-wallet')}\n" + f"Command: {' '.join(sys.argv)}\n" + f"Config: {self.config}\n\n" ) asi_logger = logging.getLogger("async_substrate_interface") asi_logger.setLevel(logging.DEBUG) From 5580ee275ca18a56a48b583c3db0ee73775badf3 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 15 Aug 2025 13:59:08 +0200 Subject: [PATCH 10/12] Add system info --- 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 41b163a5..c17fcc4f 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1263,7 +1263,9 @@ def main_callback( f"Async-Substrate-Interface: {importlib.metadata.version('async-substrate-interface')}\n" f"Bittensor-Wallet: {importlib.metadata.version('bittensor-wallet')}\n" f"Command: {' '.join(sys.argv)}\n" - f"Config: {self.config}\n\n" + f"Config: {self.config}\n" + f"Python: {sys.version}\n" + f"System: {sys.platform}\n\n" ) asi_logger = logging.getLogger("async_substrate_interface") asi_logger.setLevel(logging.DEBUG) From fac14ccc4605d4caa5f524f6808a0aa77d52f207 Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 15 Aug 2025 14:04:38 +0200 Subject: [PATCH 11/12] Additional debug info --- bittensor_cli/cli.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index c17fcc4f..df38a4f0 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -1963,6 +1963,7 @@ def wallet_overview( "Hotkeys names must be a comma-separated list, e.g., `--exclude-hotkeys hk1,hk2`.", ) logger.debug( + "args:\n" f"all_wallets: {all_wallets}\n" f"sort_by: {sort_by}\n" f"sort_order: {sort_order}\n" @@ -2060,6 +2061,7 @@ def wallet_transfer( elif not amount: amount = FloatPrompt.ask("Enter amount (in TAO) to transfer.") logger.debug( + "args:\n" f"destination: {destination_ss58_address}\n" f"amount: {amount}\n" f"transfer_all: {transfer_all}\n" @@ -2137,6 +2139,7 @@ def wallet_swap_hotkey( validate=WV.WALLET_AND_HOTKEY, ) logger.debug( + "args:\n" f"original_wallet: {original_wallet}\n" f"new_wallet: {new_wallet}\n" f"netuid: {netuid}\n" @@ -2306,6 +2309,7 @@ def wallet_faucet( validate=WV.WALLET, ) logger.debug( + "args:\n" f"network {network}\n" f"threads_per_block {threads_per_block}\n" f"update_interval {update_interval}\n" @@ -2708,6 +2712,7 @@ def wallet_associate_hotkey( f"[{COLORS.GENERAL.HK}]({hotkey_ss58})[/{COLORS.GENERAL.HK}]" ) logger.debug( + "args:\n" f"network {network}\n" f"hotkey_ss58 {hotkey_ss58}\n" f"hotkey_display {hotkey_display}\n" @@ -2871,6 +2876,7 @@ def wallet_check_ck_swap( print_error("Invalid block number") raise typer.Exit() logger.debug( + "args:\n" f"scheduled_block {scheduled_block}\n" f"ss58_address {ss58_address}\n" f"network {network}\n" @@ -3041,6 +3047,7 @@ def wallet_balance( validate=validate, ) logger.debug( + "args:\n" f"all_balances {all_balances}\n" f"ss58_addresses {ss58_addresses}\n" f"network {network}" @@ -3195,7 +3202,7 @@ def wallet_set_id( additional, github_repo, ) - logger.debug(f"identity {identity}\nnetwork {network}\n") + logger.debug(f"args:\nidentity {identity}\nnetwork {network}\n") return self._run_command( wallets.set_id( @@ -3458,6 +3465,7 @@ def wallet_swap_coldkey( ) new_wallet_coldkey_ss58 = new_wallet.coldkeypub.ss58_address logger.debug( + "args:\n" f"network {network}\n" f"new_coldkey_ss58 {new_wallet_coldkey_ss58}\n" f"force_swap {force_swap}" @@ -3534,6 +3542,7 @@ def stake_list( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) logger.debug( + "args:\n" f"coldkey_ss58 {coldkey_ss58}\n" f"network {network}\n" f"live: {live}\n" @@ -3809,6 +3818,7 @@ def stake_add( ) raise typer.Exit() logger.debug( + "args:\n" f"network: {network}\n" f"netuids: {netuids}\n" f"stake_all: {stake_all}\n" @@ -4090,6 +4100,7 @@ def stake_remove( validate=WV.WALLET_AND_HOTKEY, ) logger.debug( + "args:\n" f"network: {network}\n" f"hotkey_ss58_address: {hotkey_ss58_address}\n" f"unstake_all: {unstake_all}\n" @@ -4151,6 +4162,7 @@ def stake_remove( ) return False logger.debug( + "args:\n" f"network: {network}\n" f"hotkey_ss58_address: {hotkey_ss58_address}\n" f"all_hotkeys: {all_hotkeys}\n" @@ -4330,6 +4342,7 @@ def stake_move( "Enter the [blue]destination subnet[/blue] (netuid) to move stake to" ) logger.debug( + "args:\n" f"network: {network}\n" f"origin_netuid: {origin_netuid}\n" f"origin_hotkey: {origin_hotkey}\n" @@ -4506,6 +4519,7 @@ def stake_transfer( "Enter the [blue]destination subnet[/blue] (netuid)" ) logger.debug( + "args:\n" f"network: {network}\n" f"origin_hotkey: {origin_hotkey}\n" f"origin_netuid: {origin_netuid}\n" @@ -4624,6 +4638,7 @@ def stake_swap( if not amount and not swap_all: amount = FloatPrompt.ask("Enter the [blue]amount[/blue] to swap") logger.debug( + "args:\n" f"network: {network}\n" f"origin_netuid: {origin_netuid}\n" f"dest_netuid: {dest_netuid}\n" @@ -4784,6 +4799,7 @@ def stake_set_children( validate=WV.WALLET_AND_HOTKEY, ) logger.debug( + "args:\n" f"network: {network}\n" f"netuid: {netuid}\n" f"children: {children}\n" @@ -4857,6 +4873,7 @@ def stake_revoke_children( "Enter netuid (leave blank for all)", default=None, show_default=True ) logger.debug( + "args:\n" f"network: {network}\n" f"netuid: {netuid}\n" f"wait_for_inclusion: {wait_for_inclusion}\n" @@ -4941,6 +4958,7 @@ def stake_childkey_take( "Enter netuid (leave blank for all)", default=None, show_default=True ) logger.debug( + "args:\n" f"network: {network}\n" f"netuid: {netuid}\n" f"take: {take}\n" @@ -5083,6 +5101,7 @@ def sudo_set( wallet_name, wallet_path, wallet_hotkey, ask_for=[WO.NAME, WO.PATH] ) logger.debug( + "args:\n" f"network: {network}\n" f"netuid: {netuid}\n" f"param_name: {param_name}\n" @@ -5208,7 +5227,7 @@ def sudo_senate_vote( ask_for=[WO.NAME, WO.PATH, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) - logger.debug(f"network: {network}\nproposal: {proposal}\nvote: {vote}\n") + logger.debug(f"args:\nnetwork: {network}\nproposal: {proposal}\nvote: {vote}\n") return self._run_command( sudo.senate_vote( wallet, self.initialize_chain(network), proposal, vote, prompt @@ -5261,7 +5280,7 @@ def sudo_set_take( f"Take value must be between {min_value} and {max_value}. Provided value: {take}" ) raise typer.Exit() - logger.debug(f"network: {network}\ntake: {take}") + logger.debug(f"args:\nnetwork: {network}\ntake: {take}") result = self._run_command( sudo.set_take(wallet, self.initialize_chain(network), take) ) @@ -5605,7 +5624,7 @@ def subnets_create( logo_url=logo_url, additional=additional_info, ) - logger.debug(f"network: {network}\nidentity: {identity}\n") + logger.debug(f"args:\nnetwork: {network}\nidentity: {identity}\n") self._run_command( subnets.create( wallet, self.initialize_chain(network), identity, json_output, prompt @@ -5667,7 +5686,7 @@ def subnets_start( ], validate=WV.WALLET, ) - logger.debug(f"network: {network}\nnetuid: {netuid}\n") + logger.debug(f"args:\nnetwork: {network}\nnetuid: {netuid}\n") return self._run_command( subnets.start_subnet( wallet, @@ -5783,7 +5802,9 @@ def subnets_set_identity( logo_url=logo_url, additional=additional_info, ) - logger.debug(f"network: {network}\nnetuid: {netuid}\nidentity: {identity}") + logger.debug( + f"args:\nnetwork: {network}\nnetuid: {netuid}\nidentity: {identity}" + ) success = self._run_command( subnets.set_identity( wallet, self.initialize_chain(network), netuid, identity, prompt @@ -5921,7 +5942,7 @@ def subnets_register( ask_for=[WO.NAME, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) - logger.debug(f"network: {network}\nnetuid: {netuid}\nperiod: {period}\n") + logger.debug(f"args:\nnetwork: {network}\nnetuid: {netuid}\nperiod: {period}\n") return self._run_command( subnets.register( wallet, @@ -6365,6 +6386,7 @@ def liquidity_add( err_console.print("The low price must be lower than the high price.") return False logger.debug( + f"args:\n" f"hotkey: {hotkey}\n" f"netuid: {netuid}\n" f"liquidity: {liquidity_}\n" @@ -6472,6 +6494,7 @@ def liquidity_remove( return_wallet_and_hotkey=True, ) logger.debug( + f"args:\n" f"network: {network}\n" f"hotkey: {hotkey}\n" f"netuid: {netuid}\n" @@ -6543,6 +6566,7 @@ def liquidity_modify( negative_allowed=True, ) logger.debug( + f"args:\n" f"network: {network}\n" f"hotkey: {hotkey}\n" f"netuid: {netuid}\n" From 5ab9e8bdae2593aaf2e609f865c51ff9050214fd Mon Sep 17 00:00:00 2001 From: bdhimes Date: Fri, 15 Aug 2025 14:07:08 +0200 Subject: [PATCH 12/12] Update README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6d8b6abf..e3d8d38a 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,8 @@ to and from the chain. If you encounter an issue, and would like to save the file somewhere it won't be overwritten, run `btcli --debug`, and set the save file location. We recommend doing this first before anything, and then starting your debugging with -us on our [Discord](https://discord.gg/bittensor). +us on our [Discord](https://discord.gg/bittensor), or by opening an issue on [GitHub](https://github.com/opentensor/btcli/issues/new) +(where you can also upload your debug file). ---