From d07ee712710191bf68f286733cdbee5b1c098e8a Mon Sep 17 00:00:00 2001 From: opendansor Date: Mon, 19 Aug 2024 20:11:01 -0700 Subject: [PATCH 01/27] Child Hotkey refactor --- bittensor/commands/stake.py | 42 +++++++++++++++++++++++---------- bittensor/commands/unstake.py | 6 ++--- bittensor/extrinsics/staking.py | 7 +++++- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 3061ea7f7..3e2a2c69d 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -618,8 +618,9 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): netuid = cli.config.netuid total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and total_subnets <= netuid <= 0: - raise ValueError("Netuid is outside the current subnet range") + if total_subnets is not None and not (0 <= netuid <= total_subnets): + console.print("Netuid is outside the current subnet range") + return if not cli.config.is_set("hotkey"): cli.config.hotkey = Prompt.ask("Enter parent hotkey (ss58)") @@ -644,10 +645,11 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): netuid=cli.config.netuid, render_table=True, ) - raise ValueError( + console.print( f"There are already children hotkeys under parent hotkey {cli.config.hotkey}. " f"Call revoke_children command before attempting to set_children again, or call the get_children command to view them." ) + return if not cli.config.is_set("children"): cli.config.children = Prompt.ask( @@ -660,13 +662,15 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): if not wallet_utils.is_valid_ss58_address(child): console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") return - + console.print(f"the number of children is {len(children)}") if ( len(children) == 1 ): # if only one child, then they have full proportion by default cli.config.proportions = 1.0 + console.print(f"setting proportion to 1: {cli.config.proportions}") if not cli.config.is_set("proportions"): + console.print(f"the proportion val is {cli.config.proportions}") cli.config.proportions = Prompt.ask( "Enter the percentage of proportion for each child as comma-separated values (total must equal 1)" ) @@ -675,11 +679,16 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): proportions = [float(x) for x in re.split(r"[ ,]+", cli.config.proportions)] total_proposed = sum(proportions) if total_proposed != 1: - raise ValueError( - f"Invalid proportion: The sum of all proportions must equal 1 (representing 100% of the allocation). Proposed sum of proportions is {total_proposed}." - ) + console.print(f"Invalid proportion: The sum of all proportions must equal 1 (representing 100% of the allocation). Proposed sum of proportions is {total_proposed}.") + return + + if len(proportions) != len(children): + console.print("Invalid proportion and children length: The count of children and number of proportion values entered do not match.") + return children_with_proportions = list(zip(proportions, children)) + + SetChildrenCommand.print_current_stake(subtensor=subtensor, children=children, hotkey=cli.config.hotkey) success, message = subtensor.set_children( wallet=wallet, @@ -737,7 +746,7 @@ def add_args(parser: argparse.ArgumentParser): "--wait_for_inclusion", dest="wait_for_inclusion", action="store_true", - default=False, + default=True, help="""Wait for the transaction to be included in a block.""", ) set_children_parser.add_argument( @@ -751,12 +760,22 @@ def add_args(parser: argparse.ArgumentParser): "--prompt", dest="prompt", action="store_true", - default=False, + default=True, help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(set_children_parser) bittensor.subtensor.add_args(set_children_parser) + @staticmethod + def print_current_stake(subtensor, children, hotkey): + parent_stake = subtensor.get_total_stake_for_hotkey( + ss58_address=hotkey + ) + console.print(f"Parent Hotkey: {hotkey} | Total Parent Stake: {parent_stake}τ") + for child in children: + child_stake = subtensor.get_total_stake_for_hotkey(child) + console.print(f"Child Hotkey: {child} | Current Child Stake: {child_stake}τ") + class GetChildrenCommand: """ @@ -918,9 +937,8 @@ def render_table( table.add_column("Index", style="cyan", no_wrap=True, justify="right") table.add_column("ChildHotkey", style="cyan", no_wrap=True) table.add_column("Proportion", style="cyan", no_wrap=True, justify="right") - table.add_column("Child Stake", style="cyan", no_wrap=True, justify="right") table.add_column( - "Total Stake Weight", style="cyan", no_wrap=True, justify="right" + "New Stake Weight", style="cyan", no_wrap=True, justify="right" ) if not children: @@ -981,7 +999,6 @@ def render_table( str(i), hotkey, proportion_str, - str(stake.tao), str(stake_weight), ) @@ -990,7 +1007,6 @@ def render_table( "", "Total", f"{total_proportion}%", - f"{total_stake}τ", f"{total_stake_weight}τ", ) console.print(table) diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index cb51c081b..9d74c8d64 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -405,21 +405,21 @@ def add_args(parser: argparse.ArgumentParser): "--wait_for_inclusion", dest="wait_for_inclusion", action="store_true", - default=False, + default=True, help="""Wait for the transaction to be included in a block.""", ) parser.add_argument( "--wait_for_finalization", dest="wait_for_finalization", action="store_true", - default=False, + default=True, help="""Wait for the transaction to be finalized.""", ) parser.add_argument( "--prompt", dest="prompt", action="store_true", - default=False, + default=True, help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(parser) diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 864b29a6c..21a0e8e99 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -26,6 +26,8 @@ from bittensor.utils.balance import Balance +console = bittensor.__console__ + def _check_threshold_amount( subtensor: "bittensor.subtensor", stake_balance: Balance @@ -606,6 +608,7 @@ def set_children_extrinsic( if not all_revoked else children_with_proportions ) + console.print(f"setting children with values {normalized_children}") success, error_message = subtensor._do_set_children( wallet=wallet, @@ -615,7 +618,9 @@ def set_children_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - + console.print( + f"wait_for_inclusion: {wait_for_inclusion} wait_for_finalization: {wait_for_finalization}" + ) if not wait_for_finalization and not wait_for_inclusion: return ( True, From 71a62a50c7e991ed25893a3dcb02f66b92c552ae Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 20 Aug 2024 12:44:37 -0700 Subject: [PATCH 02/27] Child Hotkey refactor --- bittensor/commands/stake.py | 31 ++++++++++--------- bittensor/extrinsics/staking.py | 53 +++++++++++++++------------------ bittensor/utils/formatting.py | 4 +-- 3 files changed, 43 insertions(+), 45 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 3e2a2c69d..decdeddcf 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -610,6 +610,7 @@ def run(cli: "bittensor.cli"): @staticmethod def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): + console = Console() wallet = bittensor.wallet(config=cli.config) # Get values if not set. @@ -662,28 +663,26 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): if not wallet_utils.is_valid_ss58_address(child): console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") return - console.print(f"the number of children is {len(children)}") + if ( len(children) == 1 ): # if only one child, then they have full proportion by default cli.config.proportions = 1.0 - console.print(f"setting proportion to 1: {cli.config.proportions}") if not cli.config.is_set("proportions"): - console.print(f"the proportion val is {cli.config.proportions}") cli.config.proportions = Prompt.ask( "Enter the percentage of proportion for each child as comma-separated values (total must equal 1)" ) # extract proportions and child addresses from cli input - proportions = [float(x) for x in re.split(r"[ ,]+", cli.config.proportions)] + proportions = [float(x) for x in re.split(r"[ ,]+", str(cli.config.proportions))] total_proposed = sum(proportions) if total_proposed != 1: - console.print(f"Invalid proportion: The sum of all proportions must equal 1 (representing 100% of the allocation). Proposed sum of proportions is {total_proposed}.") + console.print(f":cross_mark:[red]Invalid proportion: The sum of all proportions must equal 1 (representing 100% of the allocation). Proposed sum of proportions is {total_proposed}.[/red]") return if len(proportions) != len(children): - console.print("Invalid proportion and children length: The count of children and number of proportion values entered do not match.") + console.print(":cross_mark:[red]Invalid proportion and children length: The count of children and number of proportion values entered do not match.[/red]") return children_with_proportions = list(zip(proportions, children)) @@ -702,12 +701,13 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Result if success: - GetChildrenCommand.retrieve_children( - subtensor=subtensor, - hotkey=cli.config.hotkey, - netuid=cli.config.netuid, - render_table=True, - ) + if cli.config.wait_for_finalization and cli.config.wait_for_inclusion: + GetChildrenCommand.retrieve_children( + subtensor=subtensor, + hotkey=cli.config.hotkey, + netuid=cli.config.netuid, + render_table=True, + ) console.print( ":white_heavy_check_mark: [green]Set children hotkeys.[/green]" ) @@ -768,6 +768,7 @@ def add_args(parser: argparse.ArgumentParser): @staticmethod def print_current_stake(subtensor, children, hotkey): + console = Console() parent_stake = subtensor.get_total_stake_for_hotkey( ss58_address=hotkey ) @@ -817,12 +818,14 @@ def run(cli: "bittensor.cli"): @staticmethod def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Get values if not set. + console = Console() if not cli.config.is_set("netuid"): cli.config.netuid = int(Prompt.ask("Enter netuid")) netuid = cli.config.netuid total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and total_subnets <= netuid <= 0: - raise ValueError("Netuid is outside the current subnet range") + if total_subnets is not None and not (0 <= netuid <= total_subnets): + console.print("Netuid is outside the current subnet range") + return # Get values if not set. if not cli.config.is_set("hotkey"): diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 21a0e8e99..197cf46aa 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -18,6 +18,7 @@ # DEALINGS IN THE SOFTWARE. from rich.prompt import Confirm +from rich.console import Console from time import sleep from typing import List, Union, Optional, Tuple @@ -571,10 +572,11 @@ def set_children_extrinsic( # Decrypt coldkey. wallet.coldkey + console = Console() user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. if hotkey != user_hotkey_ss58: - raise ValueError("Can only call children for other hotkeys.") + raise ValueError("Cannot set/revoke yourself as child hotkey.") # Check if all children are being revoked all_revoked = all(prop == 0.0 for prop, _ in children_with_proportions) @@ -608,7 +610,6 @@ def set_children_extrinsic( if not all_revoked else children_with_proportions ) - console.print(f"setting children with values {normalized_children}") success, error_message = subtensor._do_set_children( wallet=wallet, @@ -618,9 +619,7 @@ def set_children_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - console.print( - f"wait_for_inclusion: {wait_for_inclusion} wait_for_finalization: {wait_for_finalization}" - ) + if not wait_for_finalization and not wait_for_inclusion: return ( True, @@ -652,13 +651,10 @@ def set_children_extrinsic( def prepare_child_proportions(children_with_proportions): """ - Convert proportions to u64 and normalize + Convert proportions to u64 and normalize. """ - children_u64 = [ - (float_to_u64(prop), child) for prop, child in children_with_proportions - ] - normalized_children = normalize_children_and_proportions(children_u64) - return normalized_children + children_u64 = [(float_to_u64(proportion), child) for proportion, child in children_with_proportions] + return normalize_children_and_proportions(children_u64) def normalize_children_and_proportions( @@ -667,22 +663,21 @@ def normalize_children_and_proportions( """ Normalizes the proportions of children so that they sum to u64::MAX. """ - total = sum(prop for prop, _ in children) - u64_max = 2**64 - 1 - normalized_children = [ - (int(floor(prop * (u64_max - 1) / total)), child) for prop, child in children - ] - sum_norm = sum(prop for prop, _ in normalized_children) - - # if the sum is more, subtract the excess from the first child - if sum_norm > u64_max: - if abs(sum_norm - u64_max) > 10: - raise ValueError( - "The sum of normalized proportions is out of the acceptable range." - ) - normalized_children[0] = ( - normalized_children[0][0] - (sum_norm - (u64_max - 1)), - normalized_children[0][1], + u64_max = 2 ** 64 - 1 + total_proportions = sum(proportion for proportion, _ in children) + + # Adjust the proportions + normalized_children_u64 = [ + (floor(proportion * u64_max / total_proportions) if proportion != total_proportions else u64_max, child) + for proportion, child in children] + + # Compensate for any rounding errors + total_normalized_proportions = sum(proportion for proportion, _ in normalized_children_u64) + if total_normalized_proportions != u64_max: + max_proportion_child_index = max(range(len(normalized_children_u64)), + key=lambda index: normalized_children_u64[index][0]) + normalized_children_u64[max_proportion_child_index] = ( + normalized_children_u64[max_proportion_child_index][0] + u64_max - total_normalized_proportions, + normalized_children_u64[max_proportion_child_index][1], ) - - return normalized_children + return normalized_children_u64 \ No newline at end of file diff --git a/bittensor/utils/formatting.py b/bittensor/utils/formatting.py index 46dfc7f8f..1042fe5ab 100644 --- a/bittensor/utils/formatting.py +++ b/bittensor/utils/formatting.py @@ -59,11 +59,11 @@ def u16_to_float(value: int) -> float: def float_to_u64(value: float) -> int: # Ensure the input is within the expected range - if not (0 <= value < 1): + if not (0 <= value <= 1): raise ValueError("Input value must be between 0 and 1") # Convert the float to a u64 value, take the floor value - return int(math.floor((value * (2**64 - 1)))) - 1 + return int(math.floor((value * (2 ** 64 - 1)))) def u64_to_float(value: int) -> float: From 65d3e2e6b14db85d15bea26e056cb1f3b9d30e3b Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 20 Aug 2024 14:35:26 -0700 Subject: [PATCH 03/27] Child Hotkey refactor --- bittensor/commands/stake.py | 14 ++++++-------- bittensor/commands/unstake.py | 2 +- .../e2e_tests/subcommands/stake/test_childkeys.py | 12 ++++-------- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index decdeddcf..9c5bc4e2e 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -760,7 +760,7 @@ def add_args(parser: argparse.ArgumentParser): "--prompt", dest="prompt", action="store_true", - default=True, + default=False, help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(set_children_parser) @@ -772,10 +772,13 @@ def print_current_stake(subtensor, children, hotkey): parent_stake = subtensor.get_total_stake_for_hotkey( ss58_address=hotkey ) - console.print(f"Parent Hotkey: {hotkey} | Total Parent Stake: {parent_stake}τ") + console.print( + f"Parent HotKey: {hotkey} | ", style="cyan", end="", no_wrap=True + ) + console.print(f"Total Parent Stake: {parent_stake}τ") for child in children: child_stake = subtensor.get_total_stake_for_hotkey(child) - console.print(f"Child Hotkey: {child} | Current Child Stake: {child_stake}τ") + console.print(f"Child Hotkey: {child} | Current Child Stake: {child_stake}τ") class GetChildrenCommand: @@ -956,11 +959,6 @@ def render_table( ) return - console.print( - f"Parent HotKey: {hotkey} | ", style="cyan", end="", no_wrap=True - ) - console.print(f"Total Parent Stake: {hotkey_stake.tao}τ") - # calculate totals total_proportion = 0 total_stake = 0 diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index 9d74c8d64..f5d3a52ca 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -419,7 +419,7 @@ def add_args(parser: argparse.ArgumentParser): "--prompt", dest="prompt", action="store_true", - default=True, + default=False, help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(parser) diff --git a/tests/e2e_tests/subcommands/stake/test_childkeys.py b/tests/e2e_tests/subcommands/stake/test_childkeys.py index d29fb877d..42cc5202d 100644 --- a/tests/e2e_tests/subcommands/stake/test_childkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_childkeys.py @@ -125,14 +125,10 @@ async def wait(): ], ) output = capsys.readouterr().out - assert ( - "Parent HotKey: 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY | Total Parent Stake: 100000.0" - in output - ) - assert "ChildHotkey ┃ Proportion" in output - assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92U… │ 60.0%" in output - assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZc… │ 40.0%" in output - assert "Total │ 100.0%" in output + assert "ChildHotkey ┃ Proportion" in output + assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJ… │ 60.0%" in output + assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj6… │ 40.0%" in output + assert "Total │ 100.0%" in output await wait() From d08256355301dbd5203fdbc51a9a648bfa9b76db Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 21 Aug 2024 12:17:51 -0700 Subject: [PATCH 04/27] Child take --- bittensor/cli.py | 3 +- bittensor/commands/__init__.py | 1 + bittensor/commands/stake.py | 345 +++++++++++++++--- bittensor/extrinsics/staking.py | 93 ++++- bittensor/subtensor.py | 127 ++++++- tests/e2e_tests/Dockerfile | 44 +++ .../multistep/neurons/templates repository | 1 + .../stake/neurons/templates repository | 1 + .../subcommands/stake/test_childkeys.py | 55 +++ 9 files changed, 620 insertions(+), 50 deletions(-) create mode 100644 tests/e2e_tests/Dockerfile create mode 160000 tests/e2e_tests/multistep/neurons/templates repository create mode 160000 tests/e2e_tests/subcommands/stake/neurons/templates repository diff --git a/bittensor/cli.py b/bittensor/cli.py index e86fa013c..f74d2555c 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -72,7 +72,7 @@ CheckColdKeySwapCommand, SetChildrenCommand, GetChildrenCommand, - RevokeChildrenCommand, + RevokeChildrenCommand, SetChildKeyTakeCommand, ) # Create a console instance for CLI display. @@ -175,6 +175,7 @@ "get_children": GetChildrenCommand, "set_children": SetChildrenCommand, "revoke_children": RevokeChildrenCommand, + "set_childkey_take": SetChildKeyTakeCommand, }, }, "weights": { diff --git a/bittensor/commands/__init__.py b/bittensor/commands/__init__.py index 0692253a4..5b701b916 100644 --- a/bittensor/commands/__init__.py +++ b/bittensor/commands/__init__.py @@ -67,6 +67,7 @@ StakeShow, SetChildrenCommand, GetChildrenCommand, + SetChildKeyTakeCommand, ) from .unstake import UnStakeCommand, RevokeChildrenCommand from .overview import OverviewCommand diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 9c5bc4e2e..b3fe4d8fe 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -173,7 +173,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # If the max_stake is greater than the current wallet balance, stake the entire balance. stake_amount_tao: float = min(stake_amount_tao, wallet_balance.tao) if ( - stake_amount_tao <= 0.00001 + stake_amount_tao <= 0.00001 ): # Threshold because of fees, might create a loop otherwise # Skip hotkey if max_stake is less than current stake. continue @@ -196,13 +196,13 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Ask to stake if not config.no_prompt: if not Confirm.ask( - f"Do you want to stake to the following keys from {wallet.name}:\n" - + "".join( - [ - f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" - for hotkey, amount in zip(final_hotkeys, final_amounts) - ] - ) + f"Do you want to stake to the following keys from {wallet.name}:\n" + + "".join( + [ + f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" + for hotkey, amount in zip(final_hotkeys, final_amounts) + ] + ) ): return None @@ -231,24 +231,24 @@ def check_config(cls, config: "bittensor.config"): config.wallet.name = str(wallet_name) if ( - not config.is_set("wallet.hotkey") - and not config.no_prompt - and not config.wallet.get("all_hotkeys") - and not config.wallet.get("hotkeys") + not config.is_set("wallet.hotkey") + and not config.no_prompt + and not config.wallet.get("all_hotkeys") + and not config.wallet.get("hotkeys") ): hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) # Get amount. if ( - not config.get("amount") - and not config.get("stake_all") - and not config.get("max_stake") + not config.get("amount") + and not config.get("stake_all") + and not config.get("max_stake") ): if not Confirm.ask( - "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", defaults.wallet.name) - ) + "Stake all Tao from account: [bold]'{}'[/bold]?".format( + config.wallet.get("name", defaults.wallet.name) + ) ): amount = Prompt.ask("Enter Tao amount to stake") try: @@ -327,8 +327,8 @@ def _get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: path=wallet.path, name=wallet.name, hotkey=hotkey_file_name ) if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() + hotkey_for_name.hotkey_file.exists_on_device() + and not hotkey_for_name.hotkey_file.is_encrypted() ): hotkey_wallets.append(hotkey_for_name) except Exception: @@ -391,7 +391,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) def get_stake_accounts( - wallet, subtensor + wallet, subtensor ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Get stake account details for the given wallet. @@ -420,7 +420,7 @@ def get_stake_accounts( } def get_stakes_from_hotkeys( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from hotkeys for the provided wallet. @@ -437,8 +437,8 @@ def get_stakes_from_hotkeys( [ n.emission for n in subtensor.get_all_neurons_for_pubkey( - hot.hotkey.ss58_address - ) + hot.hotkey.ss58_address + ) ] ) hotkey_stake = subtensor.get_stake_for_coldkey_and_hotkey( @@ -453,7 +453,7 @@ def get_stakes_from_hotkeys( return stakes def get_stakes_from_delegates( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from delegates for the provided wallet. @@ -479,13 +479,13 @@ def get_stakes_from_delegates( "name": delegate_name, "stake": nom[1], "rate": dele.total_daily_return.tao - * (nom[1] / dele.total_stake.tao), + * (nom[1] / dele.total_stake.tao), } return stakes def get_all_wallet_accounts( - wallets, - subtensor, + wallets, + subtensor, ) -> List[Dict[str, Dict[str, Union[str, Balance]]]]: """Fetch stake accounts for all provided wallets using a ThreadPool. @@ -550,9 +550,9 @@ def get_all_wallet_accounts( @staticmethod def check_config(config: "bittensor.config"): if ( - not config.get("all", d=None) - and not config.is_set("wallet.name") - and not config.no_prompt + not config.get("all", d=None) + and not config.is_set("wallet.name") + and not config.no_prompt ): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) @@ -573,6 +573,259 @@ def add_args(parser: argparse.ArgumentParser): bittensor.subtensor.add_args(list_parser) +class SetChildKeyTakeCommand: + """ + Executes the ``set_childkey_take`` command to modify your childkey take on a specified subnet on the Bittensor network to the caller. + + This command is used to modify your childkey take on a specified subnet on the Bittensor network. + + Usage: + Users can specify the amount or 'take' for their child hotkeys (``SS58`` address), + the user needs to have access to the ss58 hotkey this call, and the take must be between 0 and 18%. + + The command prompts for confirmation before executing the set_childkey_take operation. + + Example usage:: + + btcli stake set_childkey_take --hotkey --netuid 1 --take 0.18 + """ + + @staticmethod + def run(cli: "bittensor.cli"): + """Set childkey take.""" + try: + subtensor: "bittensor.subtensor" = bittensor.subtensor( + config=cli.config, log_verbose=False + ) + SetChildKeyTakeCommand._run(cli, subtensor) + finally: + if "subtensor" in locals(): + subtensor.close() + bittensor.logging.debug("closing subtensor connection") + + @staticmethod + def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): + console = Console() + wallet = bittensor.wallet(config=cli.config) + + # Get values if not set. + if not cli.config.is_set("netuid"): + cli.config.netuid = int(Prompt.ask("Enter netuid")) + + netuid = cli.config.netuid + total_subnets = subtensor.get_total_subnets() + if total_subnets is not None and not (0 <= netuid <= total_subnets): + console.print("Netuid is outside the current subnet range") + return + + if not cli.config.is_set("hotkey"): + cli.config.hotkey = Prompt.ask("Enter child hotkey (ss58)") + if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + console.print( + f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + ) + return + + if not cli.config.is_set("take"): + cli.config.take = Prompt.ask( + "Enter the percentage of take for your child hotkey (between 0 and 0.18 representing 0-18%)" + ) + + # extract take from cli input + try: + take = float(cli.config.take) + except ValueError: + print(":cross_mark:[red]Take must be a float value using characters between 0 and 9.[/red]") + return + + if take < 0 or take > 0.18: + console.print( + f":cross_mark:[red]Invalid take: Childkey Take must be between 0 and 0.18 (representing 0% to 18%). Proposed take is {take}.[/red]") + return + + success, message = subtensor.set_childkey_take( + wallet=wallet, + netuid=netuid, + hotkey=cli.config.hotkey, + take=take, + wait_for_inclusion=cli.config.wait_for_inclusion, + wait_for_finalization=cli.config.wait_for_finalization, + prompt=cli.config.prompt, + ) + + # Result + if success: + console.print( + ":white_heavy_check_mark: [green]Set childkey take.[/green]" + ) + else: + console.print( + f":cross_mark:[red] Unable to set childkey take.[/red] {message}" + ) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + set_childkey_take_parser = parser.add_parser( + "set_childkey_take", help="""Set childkey take.""" + ) + set_childkey_take_parser.add_argument( + "--netuid", dest="netuid", type=int, required=False + ) + set_childkey_take_parser.add_argument( + "--hotkey", dest="hotkey", type=str, required=False + ) + set_childkey_take_parser.add_argument( + "--take", dest="take", type=float, required=False + ) + set_childkey_take_parser.add_argument( + "--wait_for_inclusion", + dest="wait_for_inclusion", + action="store_true", + default=True, + help="""Wait for the transaction to be included in a block.""", + ) + set_childkey_take_parser.add_argument( + "--wait_for_finalization", + dest="wait_for_finalization", + action="store_true", + default=True, + help="""Wait for the transaction to be finalized.""", + ) + set_childkey_take_parser.add_argument( + "--prompt", + dest="prompt", + action="store_true", + default=False, + help="""Prompt for confirmation before proceeding.""", + ) + bittensor.wallet.add_args(set_childkey_take_parser) + bittensor.subtensor.add_args(set_childkey_take_parser) + + +class GetChildKeyTakeCommand: + """ + Executes the ``get_childkey_take`` command to get your childkey take on a specified subnet on the Bittensor network to the caller. + + This command is used to get your childkey take on a specified subnet on the Bittensor network. + + Usage: + Users can get the amount or 'take' for their child hotkeys (``SS58`` address) + + Example usage:: + + btcli stake get_childkey_take --hotkey --netuid 1 + """ + + @staticmethod + def run(cli: "bittensor.cli"): + """Get childkey take.""" + try: + subtensor: "bittensor.subtensor" = bittensor.subtensor( + config=cli.config, log_verbose=False + ) + GetChildKeyTakeCommand._run(cli, subtensor) + finally: + if "subtensor" in locals(): + subtensor.close() + bittensor.logging.debug("closing subtensor connection") + + @staticmethod + def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): + console = Console() + wallet = bittensor.wallet(config=cli.config) + + # Get values if not set. + if not cli.config.is_set("netuid"): + cli.config.netuid = int(Prompt.ask("Enter netuid")) + + netuid = cli.config.netuid + total_subnets = subtensor.get_total_subnets() + if total_subnets is not None and not (0 <= netuid <= total_subnets): + console.print("Netuid is outside the current subnet range") + return + + if not cli.config.is_set("hotkey"): + cli.config.hotkey = Prompt.ask("Enter child hotkey (ss58)") + if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + console.print( + f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + ) + return + + success, message = subtensor.get_childkey_take( + wallet=wallet, + netuid=netuid, + hotkey=cli.config.hotkey, + wait_for_inclusion=cli.config.wait_for_inclusion, + wait_for_finalization=cli.config.wait_for_finalization, + prompt=cli.config.prompt, + ) + + # Result + if success: + # TODO: print success message + console.print( + ":white_heavy_check_mark: [green]Set childkey take.[/green]" + ) + else: + console.print( + f":cross_mark:[red] Unable to get childkey take.[/red] {message}" + ) + + @staticmethod + def check_config(config: "bittensor.config"): + if not config.is_set("wallet.name") and not config.no_prompt: + wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) + config.wallet.name = str(wallet_name) + if not config.is_set("wallet.hotkey") and not config.no_prompt: + hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) + config.wallet.hotkey = str(hotkey) + + @staticmethod + def add_args(parser: argparse.ArgumentParser): + get_childkey_take_parser = parser.add_parser( + "get_childkey_take", help="""Get childkey take.""" + ) + get_childkey_take_parser.add_argument( + "--netuid", dest="netuid", type=int, required=False + ) + get_childkey_take_parser.add_argument( + "--hotkey", dest="hotkey", type=str, required=False + ) + get_childkey_take_parser.add_argument( + "--wait_for_inclusion", + dest="wait_for_inclusion", + action="store_true", + default=True, + help="""Wait for the transaction to be included in a block.""", + ) + get_childkey_take_parser.add_argument( + "--wait_for_finalization", + dest="wait_for_finalization", + action="store_true", + default=True, + help="""Wait for the transaction to be finalized.""", + ) + get_childkey_take_parser.add_argument( + "--prompt", + dest="prompt", + action="store_true", + default=False, + help="""Prompt for confirmation before proceeding.""", + ) + bittensor.wallet.add_args(get_childkey_take_parser) + bittensor.subtensor.add_args(get_childkey_take_parser) + + class SetChildrenCommand: """ Executes the ``set_children`` command to add children hotkeys on a specified subnet on the Bittensor network to the caller. @@ -619,7 +872,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): netuid = cli.config.netuid total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and not (0 <= netuid <= total_subnets): + if total_subnets is not None and not (0 <= netuid < total_subnets): console.print("Netuid is outside the current subnet range") return @@ -665,7 +918,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): return if ( - len(children) == 1 + len(children) == 1 ): # if only one child, then they have full proportion by default cli.config.proportions = 1.0 @@ -678,15 +931,17 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): proportions = [float(x) for x in re.split(r"[ ,]+", str(cli.config.proportions))] total_proposed = sum(proportions) if total_proposed != 1: - console.print(f":cross_mark:[red]Invalid proportion: The sum of all proportions must equal 1 (representing 100% of the allocation). Proposed sum of proportions is {total_proposed}.[/red]") + console.print( + f":cross_mark:[red]Invalid proportion: The sum of all proportions must equal 1 (representing 100% of the allocation). Proposed sum of proportions is {total_proposed}.[/red]") return - + if len(proportions) != len(children): - console.print(":cross_mark:[red]Invalid proportion and children length: The count of children and number of proportion values entered do not match.[/red]") + console.print( + ":cross_mark:[red]Invalid proportion and children length: The count of children and number of proportion values entered do not match.[/red]") return children_with_proportions = list(zip(proportions, children)) - + SetChildrenCommand.print_current_stake(subtensor=subtensor, children=children, hotkey=cli.config.hotkey) success, message = subtensor.set_children( @@ -826,7 +1081,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): cli.config.netuid = int(Prompt.ask("Enter netuid")) netuid = cli.config.netuid total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and not (0 <= netuid <= total_subnets): + if total_subnets is not None and not (0 <= netuid < total_subnets): console.print("Netuid is outside the current subnet range") return @@ -851,7 +1106,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): @staticmethod def retrieve_children( - subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool + subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool ): """ @@ -897,12 +1152,12 @@ def add_args(parser: argparse.ArgumentParser): @staticmethod def render_table( - subtensor: "bittensor.subtensor", - hotkey: str, - hotkey_stake: "Balance", - children: list[Tuple[int, str]], - netuid: int, - prompt: bool, + subtensor: "bittensor.subtensor", + hotkey: str, + hotkey_stake: "Balance", + children: list[Tuple[int, str]], + netuid: int, + prompt: bool, ): """ diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 197cf46aa..1ba7edd89 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -18,7 +18,6 @@ # DEALINGS IN THE SOFTWARE. from rich.prompt import Confirm -from rich.console import Console from time import sleep from typing import List, Union, Optional, Tuple @@ -538,6 +537,95 @@ def __do_add_stake_single( return success +def set_childkey_take_extrinsic( + subtensor: "bittensor.subtensor", + wallet: "bittensor.wallet", + hotkey: str, + netuid: int, + take: float, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + prompt: bool = False, +) -> Tuple[bool, str]: + """ + Sets childkey take. + + Args: + subtensor (bittensor.subtensor): Subtensor endpoint to use. + wallet (bittensor.wallet): Bittensor wallet object. + hotkey (str): Childkey hotkey. + take (float): Childkey take value. + netuid (int): Unique identifier of for the subnet. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + + Returns: + Tuple[bool, Optional[str]]: A tuple containing a success flag and an optional error message. + + Raises: + bittensor.errors.ChildHotkeyError: If the extrinsic fails to be finalized or included in the block. + bittensor.errors.NotRegisteredError: If the hotkey is not registered in any subnets. + + """ + + # Decrypt coldkey. + wallet.coldkey + + user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. + if hotkey != user_hotkey_ss58: + raise ValueError("You can only set childkey take for ss58 hotkey that you own.") + + # Ask before moving on. + if prompt: + if not Confirm.ask( + f"Do you want to set childkey take to: [bold white]{take*100}%[/bold white]?" + ): + return False, "Operation Cancelled" + + with bittensor.__console__.status( + f":satellite: Setting childkey take on [white]{subtensor.network}[/white] ..." + ): + try: + + success, error_message = subtensor._do_set_childkey_take( + wallet=wallet, + hotkey=hotkey, + netuid=netuid, + take=take, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return ( + True, + "Not waiting for finalization or inclusion. Set childkey take initiated.", + ) + + if success: + bittensor.__console__.print( + ":white_heavy_check_mark: [green]Finalized[/green]" + ) + bittensor.logging.success( + prefix="Setting childkey take", + suffix="Finalized: " + str(success), + ) + return True, "Successfully set childkey take and Finalized." + else: + bittensor.__console__.print( + f":cross_mark: [red]Failed[/red]: {error_message}" + ) + bittensor.logging.warning( + prefix="Setting childkey take", + suffix="Failed: " + str(error_message), + ) + return False, error_message + + except Exception as e: + return False, f"Exception occurred while setting childkey take: {str(e)}" + + def set_children_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", @@ -572,10 +660,9 @@ def set_children_extrinsic( # Decrypt coldkey. wallet.coldkey - console = Console() user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. - if hotkey != user_hotkey_ss58: + if hotkey == user_hotkey_ss58: raise ValueError("Cannot set/revoke yourself as child hotkey.") # Check if all children are being revoked diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 05ee9bb2c..b7dbc51ce 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -101,7 +101,7 @@ from .extrinsics.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, - set_children_extrinsic, + set_children_extrinsic, set_childkey_take_extrinsic, ) from .extrinsics.transfer import transfer_extrinsic from .extrinsics.unstaking import ( @@ -2300,6 +2300,131 @@ def make_substrate_call_with_retry(): ################### # Child hotkeys # ################### + + def get_childkey_take( + self, + wallet: "bittensor.wallet", + hotkey: str, + netuid: int, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + prompt: bool = False, + ) -> tuple[bool, str]: + """Gets a childkey take extrinsic on the subnet. + + Args: + wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. + hotkey: (str): Hotkey ``ss58`` address of the child for which take is getting set. + netuid (int): Unique identifier of for the subnet. + wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. + wait_for_finalization (bool): If ``true``, waits for finalization before returning. + prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. + Returns: + success (bool): ``True`` if the extrinsic was successful. + Raises: + ChildHotkeyError: If the extrinsic failed. + """ + + return get_childkey_take_extrinsic( + self, + wallet=wallet, + hotkey=hotkey, + netuid=netuid, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + ) + + def set_childkey_take( + self, + wallet: "bittensor.wallet", + hotkey: str, + take: float, + netuid: int, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + prompt: bool = False, + ) -> tuple[bool, str]: + """Sets a childkey take extrinsic on the subnet. + + Args: + wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. + hotkey: (str): Hotkey ``ss58`` address of the child for which take is getting set. + netuid (int): Unique identifier of for the subnet. + take (float): Value of childhotkey take on subnet. + wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. + wait_for_finalization (bool): If ``true``, waits for finalization before returning. + prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. + Returns: + success (bool): ``True`` if the extrinsic was successful. + Raises: + ChildHotkeyError: If the extrinsic failed. + """ + + return set_childkey_take_extrinsic( + self, + wallet=wallet, + hotkey=hotkey, + take=take, + netuid=netuid, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + prompt=prompt, + ) + + def _do_set_childkey_take( + self, + wallet: "bittensor.wallet", + hotkey: str, + take: float, + netuid: int, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + ) -> tuple[bool, Optional[str]]: + """Sends a set_children hotkey extrinsic on the chain. + + Args: + wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. + hotkey: (str): Hotkey ``ss58`` address of the wallet for which take is getting set. + take: (float): The take that this ss58 hotkey will have if assigned as a child hotkey. + netuid (int): Unique identifier for the network. + wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. + wait_for_finalization (bool): If ``true``, waits for finalization before returning. + Returns: + success (bool): ``True`` if the extrinsic was successful. + """ + + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + def make_substrate_call_with_retry(): + # create extrinsic call + call = self.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_childkey_take", + call_params={ + "hotkey": hotkey, + "take": take, + "netuid": netuid, + }, + ) + extrinsic = self.substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = self.substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return True, None + + response.process_events() + if not response.is_success: + return False, format_error_message(response.error_message) + else: + return True, None + + return make_substrate_call_with_retry() def set_children( self, diff --git a/tests/e2e_tests/Dockerfile b/tests/e2e_tests/Dockerfile new file mode 100644 index 000000000..bbc5e6f2c --- /dev/null +++ b/tests/e2e_tests/Dockerfile @@ -0,0 +1,44 @@ +FROM ubuntu:latest + +# Install Git +RUN apt-get update && \ + apt-get install -y git + +# Install Python and dependencies +RUN apt-get install -y python3 python3-pip python3-venv curl clang libssl-dev llvm libudev-dev protobuf-compiler + +# Install Rustup non-interactively +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path + +# Set environment variables +ENV PATH="/root/.cargo/bin:${PATH}" +ENV CARGO_TERM_COLOR always +ENV RUST_BACKTRACE full + +# Install Rust toolchain and components +RUN /root/.cargo/bin/rustup toolchain install nightly-2024-03-05 && \ + /root/.cargo/bin/rustup target add wasm32-unknown-unknown --toolchain nightly-2024-03-05 && \ + /root/.cargo/bin/rustup component add rust-src --toolchain nightly-2024-03-05 + +# Create a virtual environment for Python packages +RUN python3 -m venv /opt/venv + +# Activate virtual environment and install pytest +COPY ../requirements/prod.txt /app/prod.txt +COPY ../requirements/dev.txt /app/dev.txt +RUN /opt/venv/bin/pip install --upgrade pip +RUN pip3 install -r /app/prod.txt +RUN pip3 install -r /app/dev.txt + +# Set environment variables to use the virtual environment +ENV PATH="/opt/venv/bin:$PATH" + +# Set up your repository and clone subtensor repo +WORKDIR /app +COPY . /app +RUN git clone https://github.com/opentensor/subtensor.git + +# Add any additional setup steps here + +# Set the entry point for running tests +ENTRYPOINT ["pytest", "."] diff --git a/tests/e2e_tests/multistep/neurons/templates repository b/tests/e2e_tests/multistep/neurons/templates repository new file mode 160000 index 000000000..dcaeb7ae0 --- /dev/null +++ b/tests/e2e_tests/multistep/neurons/templates repository @@ -0,0 +1 @@ +Subproject commit dcaeb7ae0e405a05388667a1aa7b0a053ae338a1 diff --git a/tests/e2e_tests/subcommands/stake/neurons/templates repository b/tests/e2e_tests/subcommands/stake/neurons/templates repository new file mode 160000 index 000000000..dcaeb7ae0 --- /dev/null +++ b/tests/e2e_tests/subcommands/stake/neurons/templates repository @@ -0,0 +1 @@ +Subproject commit dcaeb7ae0e405a05388667a1aa7b0a053ae338a1 diff --git a/tests/e2e_tests/subcommands/stake/test_childkeys.py b/tests/e2e_tests/subcommands/stake/test_childkeys.py index 42cc5202d..30d4d40d6 100644 --- a/tests/e2e_tests/subcommands/stake/test_childkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_childkeys.py @@ -8,6 +8,7 @@ RevokeChildrenCommand, GetChildrenCommand, ) +from bittensor.commands.stake import SetChildKeyTakeCommand from bittensor.extrinsics.staking import prepare_child_proportions from tests.e2e_tests.utils import setup_wallet, wait_interval @@ -170,3 +171,57 @@ async def wait(): ) output = capsys.readouterr().out assert "There are currently no child hotkeys on subnet" in output + + +@pytest.mark.asyncio +async def test_set_revoke_childkey_take(local_chain, capsys): + # todo add comment + # Setup + alice_keypair, alice_exec_command, alice_wallet = setup_wallet("//Alice") + bob_keypair, bob_exec_command, bob_wallet = setup_wallet("//Bob") + eve_keypair, eve_exec_command, eve_wallet = setup_wallet("//Eve") + + alice_exec_command(RegisterSubnetworkCommand, ["s", "create"]) + assert local_chain.query("SubtensorModule", "NetworksAdded", [1]).serialize() + + for exec_command in [alice_exec_command, bob_exec_command, eve_exec_command]: + exec_command(RegisterCommand, ["s", "register", "--netuid", "1"]) + + async def wait(): + # wait rate limit, until we are allowed to get children + + rate_limit = ( + subtensor.query_constant( + module_name="SubtensorModule", constant_name="InitialTempo" + ).value + * 2 + ) + curr_block = subtensor.get_current_block() + await wait_interval(rate_limit + curr_block + 1, subtensor) + + subtensor = bittensor.subtensor(network="ws://localhost:9945") + + # await wait() + + # Test 1: Set multiple children + alice_exec_command( + SetChildKeyTakeCommand, + [ + "stake", + "set_childkey_take", + "--netuid", + "1", + "--hotkey", + str(alice_keypair.ss58_address), + "--take", + "0.12", + "--wait_for_inclusion", + "True", + "--wait_for_finalization", + "True", + ], + ) + + await wait() + + subtensor = bittensor.subtensor(network="ws://localhost:9945") From da756632449f6d1bb534bd04425ee04fdcf0a6b6 Mon Sep 17 00:00:00 2001 From: opendansor Date: Thu, 22 Aug 2024 17:46:27 -0700 Subject: [PATCH 05/27] Child Hotkey refactor --- bittensor/cli.py | 2 + bittensor/commands/__init__.py | 1 + bittensor/commands/stake.py | 126 ++++---- bittensor/commands/unstake.py | 43 ++- bittensor/extrinsics/staking.py | 58 ++-- bittensor/subtensor.py | 67 ++-- bittensor/utils/formatting.py | 4 +- bittensor/utils/subtensor.py | 2 +- tests/e2e_tests/conftest.py | 116 ++++--- .../subcommands/stake/test_childkeys.py | 287 ++++++++++++++++-- 10 files changed, 499 insertions(+), 207 deletions(-) diff --git a/bittensor/cli.py b/bittensor/cli.py index f74d2555c..35ec78ba4 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -74,6 +74,7 @@ GetChildrenCommand, RevokeChildrenCommand, SetChildKeyTakeCommand, ) +from .commands.stake import GetChildKeyTakeCommand # Create a console instance for CLI display. console = bittensor.__console__ @@ -176,6 +177,7 @@ "set_children": SetChildrenCommand, "revoke_children": RevokeChildrenCommand, "set_childkey_take": SetChildKeyTakeCommand, + "get_childkey_take": GetChildKeyTakeCommand, }, }, "weights": { diff --git a/bittensor/commands/__init__.py b/bittensor/commands/__init__.py index 5b701b916..e1ac8c74c 100644 --- a/bittensor/commands/__init__.py +++ b/bittensor/commands/__init__.py @@ -68,6 +68,7 @@ SetChildrenCommand, GetChildrenCommand, SetChildKeyTakeCommand, + GetChildKeyTakeCommand, ) from .unstake import UnStakeCommand, RevokeChildrenCommand from .overview import OverviewCommand diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index b3fe4d8fe..71530673b 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -36,7 +36,7 @@ ) from . import defaults # type: ignore from ..utils import wallet_utils -from ..utils.formatting import u64_to_float +from ..utils.formatting import u64_to_float, u16_to_float console = bittensor.__console__ @@ -741,7 +741,6 @@ def run(cli: "bittensor.cli"): @staticmethod def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console = Console() - wallet = bittensor.wallet(config=cli.config) # Get values if not set. if not cli.config.is_set("netuid"): @@ -761,24 +760,20 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) return - success, message = subtensor.get_childkey_take( - wallet=wallet, + take_u16 = subtensor.get_childkey_take( netuid=netuid, hotkey=cli.config.hotkey, - wait_for_inclusion=cli.config.wait_for_inclusion, - wait_for_finalization=cli.config.wait_for_finalization, - prompt=cli.config.prompt, ) # Result - if success: - # TODO: print success message + if take_u16: + take = u16_to_float(take_u16) console.print( - ":white_heavy_check_mark: [green]Set childkey take.[/green]" + f"The childkey take for {cli.config.hotkey} is {take * 100}%." ) else: console.print( - f":cross_mark:[red] Unable to get childkey take.[/red] {message}" + ":cross_mark:[red] Unable to get childkey take.[/red]" ) @staticmethod @@ -801,30 +796,30 @@ def add_args(parser: argparse.ArgumentParser): get_childkey_take_parser.add_argument( "--hotkey", dest="hotkey", type=str, required=False ) - get_childkey_take_parser.add_argument( - "--wait_for_inclusion", - dest="wait_for_inclusion", - action="store_true", - default=True, - help="""Wait for the transaction to be included in a block.""", - ) - get_childkey_take_parser.add_argument( - "--wait_for_finalization", - dest="wait_for_finalization", - action="store_true", - default=True, - help="""Wait for the transaction to be finalized.""", - ) - get_childkey_take_parser.add_argument( - "--prompt", - dest="prompt", - action="store_true", - default=False, - help="""Prompt for confirmation before proceeding.""", - ) bittensor.wallet.add_args(get_childkey_take_parser) bittensor.subtensor.add_args(get_childkey_take_parser) + @staticmethod + def get_take(subtensor, hotkey, netuid) -> float: + """ + Get the take value for a given subtensor, hotkey, and netuid. + + @param subtensor: The subtensor object. + @param hotkey: The hotkey to retrieve the take value for. + @param netuid: The netuid to retrieve the take value for. + + @return: The take value as a float. If the take value is not available, it returns 0. + + """ + take_u16 = subtensor.get_childkey_take( + netuid=netuid, + hotkey=hotkey, + ) + if take_u16: + return u16_to_float(take_u16) + else: + return 0 + class SetChildrenCommand: """ @@ -876,6 +871,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print("Netuid is outside the current subnet range") return + # get parent hotkey if not cli.config.is_set("hotkey"): cli.config.hotkey = Prompt.ask("Enter parent hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): @@ -884,7 +880,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) return - # get children + # get current children curr_children = GetChildrenCommand.retrieve_children( subtensor=subtensor, hotkey=cli.config.hotkey, @@ -893,56 +889,59 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) if curr_children: + # print the table of current children GetChildrenCommand.retrieve_children( subtensor=subtensor, hotkey=cli.config.hotkey, netuid=cli.config.netuid, render_table=True, ) - console.print( - f"There are already children hotkeys under parent hotkey {cli.config.hotkey}. " - f"Call revoke_children command before attempting to set_children again, or call the get_children command to view them." - ) - return + # get new children if not cli.config.is_set("children"): cli.config.children = Prompt.ask( "Enter child(ren) hotkeys (ss58) as comma-separated values" ) - children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] + proposed_children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] + + # Set max 5 children + if len(proposed_children) + len(curr_children) > 5: + console.print( + ":cross_mark:[red] Too many children. Maximum 5 children per hotkey[/red]" + ) + return # Validate children SS58 addresses - for child in children: + for child in proposed_children: if not wallet_utils.is_valid_ss58_address(child): console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") return - if ( - len(children) == 1 - ): # if only one child, then they have full proportion by default - cli.config.proportions = 1.0 - + # get proportions for new children if not cli.config.is_set("proportions"): cli.config.proportions = Prompt.ask( - "Enter the percentage of proportion for each child as comma-separated values (total must equal 1)" + "Enter the percentage of proportion for each child as comma-separated values (total from all children must be less than or equal to 1)" ) # extract proportions and child addresses from cli input proportions = [float(x) for x in re.split(r"[ ,]+", str(cli.config.proportions))] - total_proposed = sum(proportions) - if total_proposed != 1: + total_proposed = sum(proportions) + SetChildrenCommand.get_current_proportion(curr_children) + if total_proposed > 1: console.print( - f":cross_mark:[red]Invalid proportion: The sum of all proportions must equal 1 (representing 100% of the allocation). Proposed sum of proportions is {total_proposed}.[/red]") + f":cross_mark:[red]Invalid proportion: The sum of all proportions must be less or equal to than 1 (representing 100% of the allocation). Proposed sum addition is proportions is {total_proposed}.[/red]") return - if len(proportions) != len(children): + if len(proportions) != len(proposed_children): console.print( ":cross_mark:[red]Invalid proportion and children length: The count of children and number of proportion values entered do not match.[/red]") return - children_with_proportions = list(zip(proportions, children)) + # combine proposed and current children + children_with_proportions = list(zip(proportions, proposed_children)) + children_with_proportions += [(u64_to_float(child[0]), child[1]) for child in curr_children] - SetChildrenCommand.print_current_stake(subtensor=subtensor, children=children, hotkey=cli.config.hotkey) + SetChildrenCommand.print_current_stake(subtensor=subtensor, children=proposed_children, + hotkey=cli.config.hotkey) success, message = subtensor.set_children( wallet=wallet, @@ -1027,6 +1026,7 @@ def print_current_stake(subtensor, children, hotkey): parent_stake = subtensor.get_total_stake_for_hotkey( ss58_address=hotkey ) + console.print("Current Status:") console.print( f"Parent HotKey: {hotkey} | ", style="cyan", end="", no_wrap=True ) @@ -1035,6 +1035,12 @@ def print_current_stake(subtensor, children, hotkey): child_stake = subtensor.get_total_stake_for_hotkey(child) console.print(f"Child Hotkey: {child} | Current Child Stake: {child_stake}τ") + console.print("Current Status:") + + @staticmethod + def get_current_proportion(children: List[Tuple[int, str]]) -> float: + return u64_to_float(sum([child[0] for child in children])) + class GetChildrenCommand: """ @@ -1107,7 +1113,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): @staticmethod def retrieve_children( subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool - ): + ) -> list[tuple[int, str]]: """ Static method to retrieve children for a given subtensor. @@ -1198,6 +1204,7 @@ def render_table( table.add_column("Index", style="cyan", no_wrap=True, justify="right") table.add_column("ChildHotkey", style="cyan", no_wrap=True) table.add_column("Proportion", style="cyan", no_wrap=True, justify="right") + table.add_column("Childkey Take", style="cyan", no_wrap=True, justify="right") table.add_column( "New Stake Weight", style="cyan", no_wrap=True, justify="right" ) @@ -1218,6 +1225,7 @@ def render_table( total_proportion = 0 total_stake = 0 total_stake_weight = 0 + avg_take = 0 children_info = [] for child in children: @@ -1227,19 +1235,22 @@ def render_table( ss58_address=child_hotkey ) or Balance(0) + child_take = subtensor.get_childkey_take(child_hotkey, netuid) + # add to totals total_stake += child_stake.tao + avg_take += child_take proportion = u64_to_float(proportion) - children_info.append((proportion, child_hotkey, child_stake)) + children_info.append((proportion, child_hotkey, child_stake, child_take)) children_info.sort( key=lambda x: x[0], reverse=True ) # sorting by proportion (highest first) # add the children info to the table - for i, (proportion, hotkey, stake) in enumerate(children_info, 1): + for i, (proportion, hotkey, stake, child_take) in enumerate(children_info, 1): proportion_percent = proportion * 100 # Proportion in percent proportion_tao = hotkey_stake.tao * proportion # Proportion in TAO @@ -1249,20 +1260,25 @@ def render_table( proportion_str = f"{proportion_percent}% ({proportion_tao}τ)" stake_weight = stake.tao + proportion_tao total_stake_weight += stake_weight + take_str = f"{child_take * 100}%" hotkey = Text(hotkey, style="red" if proportion == 0 else "") table.add_row( str(i), hotkey, proportion_str, + take_str, str(stake_weight), ) + avg_take = avg_take / len(children_info) + # add totals row table.add_row( "", "Total", f"{total_proportion}%", + f"{avg_take * 100}%", f"{total_stake_weight}τ", ) console.print(table) diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index f5d3a52ca..efae152cf 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -18,6 +18,7 @@ import sys import argparse from typing import List, Union, Optional, Tuple +import re from rich.prompt import Confirm, Prompt from tqdm import tqdm @@ -26,6 +27,8 @@ from bittensor.utils.balance import Balance from . import defaults, GetChildrenCommand from .utils import get_hotkey_wallets_for_wallet +from ..utils import wallet_utils +from ..utils.formatting import u64_to_float console = bittensor.__console__ @@ -341,27 +344,52 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): if not cli.config.is_set("netuid"): cli.config.netuid = int(Prompt.ask("Enter netuid")) + netuid = cli.config.netuid + total_subnets = subtensor.get_total_subnets() + if total_subnets is not None and not (0 <= netuid < total_subnets): + console.print("Netuid is outside the current subnet range") + return + + # get parent hotkey if not cli.config.is_set("hotkey"): cli.config.hotkey = Prompt.ask("Enter parent hotkey (ss58)") + if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + console.print( + f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + ) + return # Get and display current children information current_children = GetChildrenCommand.retrieve_children( subtensor=subtensor, hotkey=cli.config.hotkey, netuid=cli.config.netuid, - render_table=False, + render_table=True, ) - # Parse from strings - netuid = cli.config.netuid + # get new children + if not cli.config.is_set("children"): + cli.config.children = Prompt.ask( + "Enter children hotkeys (ss58) as comma-separated values that you wish to revoke." + ) + revoked_children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] + + # Check if revoked children are in current children + current_children_set = set(child[1] for child in current_children) + for revoked_child in revoked_children: + if revoked_child not in current_children_set: + console.print( + f":cross_mark:[red] Child hotkey {revoked_child} is not present in current children hotkeys.[/red]") + return - # Prepare children with zero proportions - children_with_zero_proportions = [(0.0, child[1]) for child in current_children] + # Prepare children with zero proportions for revoked children and convert proportions of non-revoked children to float + children_with_proportions = [(0, child[1]) if child[1] in revoked_children else (u64_to_float(child[0]), child[1]) for + child in current_children] success, message = subtensor.set_children( wallet=wallet, netuid=netuid, - children_with_proportions=children_with_zero_proportions, + children_with_proportions=children_with_proportions, hotkey=cli.config.hotkey, wait_for_inclusion=cli.config.wait_for_inclusion, wait_for_finalization=cli.config.wait_for_finalization, @@ -401,6 +429,9 @@ def add_args(parser: argparse.ArgumentParser): ) parser.add_argument("--netuid", dest="netuid", type=int, required=False) parser.add_argument("--hotkey", dest="hotkey", type=str, required=False) + parser.add_argument( + "--children", dest="children", type=str, required=False + ) parser.add_argument( "--wait_for_inclusion", dest="wait_for_inclusion", diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 1ba7edd89..8bfd57409 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -1,7 +1,6 @@ # The MIT License (MIT) # Copyright © 2021 Yuma Rao # Copyright © 2023 Opentensor Foundation -from math import floor # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated # documentation files (the “Software”), to deal in the Software without restriction, including without limitation @@ -22,7 +21,7 @@ from typing import List, Union, Optional, Tuple import bittensor -from ..utils.formatting import float_to_u64 +from ..utils.formatting import float_to_u64, float_to_u16 from bittensor.utils.balance import Balance @@ -587,12 +586,17 @@ def set_childkey_take_extrinsic( f":satellite: Setting childkey take on [white]{subtensor.network}[/white] ..." ): try: + + if 0 < take < 0.18: + take_u16 = float_to_u16(take) + else: + return False, "Invalid take value" success, error_message = subtensor._do_set_childkey_take( wallet=wallet, hotkey=hotkey, netuid=netuid, - take=take, + take=take_u16, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) @@ -662,13 +666,13 @@ def set_children_extrinsic( wallet.coldkey user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. - if hotkey == user_hotkey_ss58: - raise ValueError("Cannot set/revoke yourself as child hotkey.") + if hotkey != user_hotkey_ss58: + raise ValueError("Cannot set/revoke child hotkeys for others.") # Check if all children are being revoked all_revoked = all(prop == 0.0 for prop, _ in children_with_proportions) - operation = "Revoke all children hotkeys" if all_revoked else "Set children hotkeys" + operation = "Revoke children hotkeys" if all_revoked else "Set children hotkeys" # Ask before moving on. if prompt: @@ -679,7 +683,7 @@ def set_children_extrinsic( return False, "Operation Cancelled" else: if not Confirm.ask( - "Do you want to set children hotkeys:\n[bold white]{}[/bold white]?".format( + "Do you want to set children hotkeys with proportions:\n[bold white]{}[/bold white]?".format( "\n".join( f" {child[1]}: {child[0]}" for child in children_with_proportions @@ -738,33 +742,19 @@ def set_children_extrinsic( def prepare_child_proportions(children_with_proportions): """ - Convert proportions to u64 and normalize. + Convert proportions to u64 and normalize, ensuring total does not exceed u64 max. """ children_u64 = [(float_to_u64(proportion), child) for proportion, child in children_with_proportions] - return normalize_children_and_proportions(children_u64) - - -def normalize_children_and_proportions( - children: List[Tuple[int, str]], -) -> List[Tuple[int, str]]: - """ - Normalizes the proportions of children so that they sum to u64::MAX. - """ - u64_max = 2 ** 64 - 1 - total_proportions = sum(proportion for proportion, _ in children) - - # Adjust the proportions - normalized_children_u64 = [ - (floor(proportion * u64_max / total_proportions) if proportion != total_proportions else u64_max, child) - for proportion, child in children] - - # Compensate for any rounding errors - total_normalized_proportions = sum(proportion for proportion, _ in normalized_children_u64) - if total_normalized_proportions != u64_max: - max_proportion_child_index = max(range(len(normalized_children_u64)), - key=lambda index: normalized_children_u64[index][0]) - normalized_children_u64[max_proportion_child_index] = ( - normalized_children_u64[max_proportion_child_index][0] + u64_max - total_normalized_proportions, - normalized_children_u64[max_proportion_child_index][1], + total = sum(proportion for proportion, _ in children_u64) + + if total > (2 ** 64 - 1): + excess = total - (2 ** 64 - 1) + if excess > (2 ** 64 * 0.01): # Example threshold of 1% of u64 max + raise ValueError("Excess is too great to normalize proportions") + largest_child_index = max(range(len(children_u64)), key=lambda i: children_u64[i][0]) + children_u64[largest_child_index] = ( + children_u64[largest_child_index][0] - excess, + children_u64[largest_child_index][1] ) - return normalized_children_u64 \ No newline at end of file + + return children_u64 diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index b7dbc51ce..e0d98387f 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -2301,40 +2301,6 @@ def make_substrate_call_with_retry(): # Child hotkeys # ################### - def get_childkey_take( - self, - wallet: "bittensor.wallet", - hotkey: str, - netuid: int, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - prompt: bool = False, - ) -> tuple[bool, str]: - """Gets a childkey take extrinsic on the subnet. - - Args: - wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. - hotkey: (str): Hotkey ``ss58`` address of the child for which take is getting set. - netuid (int): Unique identifier of for the subnet. - wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. - wait_for_finalization (bool): If ``true``, waits for finalization before returning. - prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. - Returns: - success (bool): ``True`` if the extrinsic was successful. - Raises: - ChildHotkeyError: If the extrinsic failed. - """ - - return get_childkey_take_extrinsic( - self, - wallet=wallet, - hotkey=hotkey, - netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - prompt=prompt, - ) - def set_childkey_take( self, wallet: "bittensor.wallet", @@ -2376,7 +2342,7 @@ def _do_set_childkey_take( self, wallet: "bittensor.wallet", hotkey: str, - take: float, + take: int, netuid: int, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -2386,7 +2352,7 @@ def _do_set_childkey_take( Args: wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. hotkey: (str): Hotkey ``ss58`` address of the wallet for which take is getting set. - take: (float): The take that this ss58 hotkey will have if assigned as a child hotkey. + take: (int): The take that this ss58 hotkey will have if assigned as a child hotkey as u16 value. netuid (int): Unique identifier for the network. wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. wait_for_finalization (bool): If ``true``, waits for finalization before returning. @@ -4718,7 +4684,34 @@ def make_substrate_call_with_retry(encoded_coldkey_: List[int]): # Child Hotkey Information # ############################ - def get_children(self, hotkey, netuid): + def get_childkey_take(self, hotkey: str, netuid: int, block: Optional[int] = None) -> Optional[float]: + """ + Get the childkey take of a hotkey on a specific network. + Args: + - hotkey (str): The hotkey to search for. + - netuid (int): The netuid to search for. + - block (Optional[int]): Optional parameter specifying the block number. Defaults to None. + + Returns: + - Optional[float]: The value of the "ChildkeyTake" if found, or None if any error occurs. + """ + try: + childkey_take = self.query_subtensor( + name="ChildkeyTake", + block=block, + params=[hotkey, netuid], + ) + if childkey_take: + return float(childkey_take.value) + + except SubstrateRequestException as e: + print(f"Error querying ChildKeys: {e}") + return None + except Exception as e: + print(f"Unexpected error in get_children: {e}") + return None + + def get_children(self, hotkey, netuid) -> list[tuple[int, str]] | list[Any] | None: """ Get the children of a hotkey on a specific network. Args: diff --git a/bittensor/utils/formatting.py b/bittensor/utils/formatting.py index 1042fe5ab..21560ea5f 100644 --- a/bittensor/utils/formatting.py +++ b/bittensor/utils/formatting.py @@ -37,7 +37,7 @@ def convert_blocks_to_time(blocks: int, block_time: int = 12) -> tuple[int, int, return hours, minutes, remaining_seconds -def float_to_u16(value: int) -> int: +def float_to_u16(value: float) -> int: # Ensure the input is within the expected range if not (0 <= value <= 1): raise ValueError("Input value must be between 0 and 1") @@ -58,6 +58,8 @@ def u16_to_float(value: int) -> float: def float_to_u64(value: float) -> int: + if value == 0.0: + return 0 # Ensure the input is within the expected range if not (0 <= value <= 1): raise ValueError("Input value must be between 0 and 1") diff --git a/bittensor/utils/subtensor.py b/bittensor/utils/subtensor.py index 279a68322..0df9c64d8 100644 --- a/bittensor/utils/subtensor.py +++ b/bittensor/utils/subtensor.py @@ -154,7 +154,7 @@ def format_parent(proportion, parent) -> Tuple[str, str]: return int_proportion, parent.value -def format_children(children) -> List[Tuple[str, str]]: +def format_children(children) -> List[Tuple[int, str]]: """ Formats raw children data into a list of tuples. Args: diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 9db51c100..923d79ce8 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -3,6 +3,7 @@ import re import shlex import signal +import socket import subprocess import time @@ -18,66 +19,87 @@ logging.basicConfig(level=logging.INFO) +# Function to check if the process is running by port +def is_chain_running(port): + """Check if a node is running on the given port.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + try: + # Attempt to connect to the given port on localhost + s.connect(("127.0.0.1", port)) + return True + except (ConnectionRefusedError, OSError): + # If the connection is refused or there's an OS error, the node is not running + return False + + # Fixture for setting up and tearing down a localnet.sh chain between tests @pytest.fixture(scope="function") def local_chain(request): param = request.param if hasattr(request, "param") else None - # Get the environment variable for the script path script_path = os.getenv("LOCALNET_SH_PATH") if not script_path: - # Skip the test if the localhost.sh path is not set logging.warning("LOCALNET_SH_PATH env variable is not set, e2e test skipped.") pytest.skip("LOCALNET_SH_PATH environment variable is not set.") - # Check if param is None, and handle it accordingly - args = "" if param is None else f"{param}" - - # compile commands to send to process - cmds = shlex.split(f"{script_path} {args}") - # Start new node process - process = subprocess.Popen( - cmds, stdout=subprocess.PIPE, text=True, preexec_fn=os.setsid - ) - - # Pattern match indicates node is compiled and ready - pattern = re.compile(r"Imported #1") + # Determine the port to check based on `param` + port = 9945 # Default port if `param` is None - # install neuron templates - logging.info("downloading and installing neuron templates from github") + # Always perform template installation + logging.info("Downloading and installing neuron templates from GitHub") templates_dir = clone_or_update_templates() install_templates(templates_dir) - timestamp = int(time.time()) - - def wait_for_node_start(process, pattern): - for line in process.stdout: - print(line.strip()) - # 20 min as timeout - if int(time.time()) - timestamp > 20 * 60: - pytest.fail("Subtensor not started in time") - if pattern.search(line): - print("Node started!") - break - - wait_for_node_start(process, pattern) - - # Run the test, passing in substrate interface - yield SubstrateInterface(url="ws://127.0.0.1:9945") - - # Terminate the process group (includes all child processes) - os.killpg(os.getpgid(process.pid), signal.SIGTERM) - - # Give some time for the process to terminate - time.sleep(1) - - # If the process is not terminated, send SIGKILL - if process.poll() is None: - os.killpg(os.getpgid(process.pid), signal.SIGKILL) - - # Ensure the process has terminated - process.wait() + already_running = False + if is_chain_running(port): + already_running = True + logging.info(f"Chain already running on port {port}, skipping start.") + else: + logging.info(f"Starting new chain on port {port}...") + # compile commands to send to process + cmds = shlex.split(f"{script_path} {param}") + # Start new node process + process = subprocess.Popen( + cmds, stdout=subprocess.PIPE, text=True, preexec_fn=os.setsid + ) + + # Wait for the node to start using the existing pattern match + pattern = re.compile(r"Imported #1") + timestamp = int(time.time()) + + def wait_for_node_start(process, pattern): + for line in process.stdout: + print(line.strip()) + if int(time.time()) - timestamp > 20 * 60: + pytest.fail("Subtensor not started in time") + if pattern.search(line): + print("Node started!") + break + + wait_for_node_start(process, pattern) + + # Continue with installing templates + logging.info("Downloading and installing neuron templates from GitHub") + templates_dir = clone_or_update_templates() + install_templates(templates_dir) - # uninstall templates - logging.info("uninstalling neuron templates") + # Run the test, passing in the substrate interface + yield SubstrateInterface(url=f"ws://127.0.0.1:{port}") + + if not already_running: + # Terminate the process group (includes all child processes) + os.killpg(os.getpgid(process.pid), signal.SIGTERM) + + # Give some time for the process to terminate + time.sleep(1) + + # If the process is not terminated, send SIGKILL + if process.poll() is None: + os.killpg(os.getpgid(process.pid), signal.SIGKILL) + + # Ensure the process has terminated + process.wait() + + logging.info("Uninstalling neuron templates") uninstall_templates(templates_dir) + \ No newline at end of file diff --git a/tests/e2e_tests/subcommands/stake/test_childkeys.py b/tests/e2e_tests/subcommands/stake/test_childkeys.py index 30d4d40d6..eeaba442a 100644 --- a/tests/e2e_tests/subcommands/stake/test_childkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_childkeys.py @@ -8,13 +8,13 @@ RevokeChildrenCommand, GetChildrenCommand, ) -from bittensor.commands.stake import SetChildKeyTakeCommand +from bittensor.commands.stake import SetChildKeyTakeCommand, GetChildKeyTakeCommand from bittensor.extrinsics.staking import prepare_child_proportions from tests.e2e_tests.utils import setup_wallet, wait_interval @pytest.mark.asyncio -async def test_set_revoke_children(local_chain, capsys): +async def test_set_revoke_children_multiple(local_chain, capsys): """ Test the setting and revoking of children hotkeys for staking. @@ -62,10 +62,10 @@ async def wait(): # wait rate limit, until we are allowed to get children rate_limit = ( - subtensor.query_constant( - module_name="SubtensorModule", constant_name="InitialTempo" - ).value - * 2 + subtensor.query_constant( + module_name="SubtensorModule", constant_name="InitialTempo" + ).value + * 2 ) curr_block = subtensor.get_current_block() await wait_interval(rate_limit + curr_block + 1, subtensor) @@ -75,8 +75,8 @@ async def wait(): await wait() children_with_proportions = [ - [0.6, bob_keypair.ss58_address], - [0.4, eve_keypair.ss58_address], + [0.4, bob_keypair.ss58_address], + [0.2, eve_keypair.ss58_address], ] # Test 1: Set multiple children @@ -109,8 +109,8 @@ async def wait(): normalized_proportions = prepare_child_proportions(children_with_proportions) assert ( - children_info[0][0] == normalized_proportions[0][0] - and children_info[1][0] == normalized_proportions[1][0] + children_info[0][0] == normalized_proportions[0][0] + and children_info[1][0] == normalized_proportions[1][0] ), "Incorrect proportions set" # Test 2: Get children information @@ -126,10 +126,10 @@ async def wait(): ], ) output = capsys.readouterr().out - assert "ChildHotkey ┃ Proportion" in output - assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJ… │ 60.0%" in output - assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj6… │ 40.0%" in output - assert "Total │ 100.0%" in output + assert "ChildHotkey" in output + assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92Uh… │ 40.0%" in output + assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcC… │ 20.0%" in output + assert "Total │ 60.0%" in output await wait() @@ -143,6 +143,8 @@ async def wait(): "1", "--hotkey", str(alice_keypair.ss58_address), + "--children", + f"{children_with_proportions[0][1]},{children_with_proportions[1][1]}", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -153,7 +155,7 @@ async def wait(): await wait() assert ( - subtensor.get_children(netuid=1, hotkey=alice_keypair.ss58_address) == [] + subtensor.get_children(netuid=1, hotkey=alice_keypair.ss58_address) == [] ), "Failed to revoke children hotkeys" await wait() @@ -175,7 +177,112 @@ async def wait(): @pytest.mark.asyncio async def test_set_revoke_childkey_take(local_chain, capsys): - # todo add comment + """ + Test the setting and retrieving of childkey take amounts for staking. + + This test case covers the following scenarios: + 1. Setting a childkey take amount for a specific hotkey + 2. Retrieving the childkey take amount + 3. Verifying the retrieved childkey take amount + + The test uses one wallet (Alice) and performs operations + on a local blockchain. + + Args: + local_chain: A fixture providing access to the local blockchain + capsys: A pytest fixture for capturing stdout and stderr + + The test performs the following steps: + - Set up wallets for Alice, Bob, and Eve + - Create a subnet and register wallets + - Set a childkey take amount for Alice + - Verify the setting operation was successful + - Retrieve the set childkey take amount + - Verify the retrieved amount is correct + + This test ensures the proper functioning of setting and retrieving + childkey take amounts in the staking system. + """ + # Setup + alice_keypair, alice_exec_command, alice_wallet = setup_wallet("//Alice") + + alice_exec_command(RegisterSubnetworkCommand, ["s", "create"]) + assert local_chain.query("SubtensorModule", "NetworksAdded", [1]).serialize() + + for exec_command in [alice_exec_command]: + exec_command(RegisterCommand, ["s", "register", "--netuid", "1"]) + + # Test 1: Set multiple children + alice_exec_command( + SetChildKeyTakeCommand, + [ + "stake", + "set_childkey_take", + "--netuid", + "1", + "--hotkey", + str(alice_keypair.ss58_address), + "--take", + "0.12", + "--wait_for_inclusion", + "True", + "--wait_for_finalization", + "True", + ], + ) + + output = capsys.readouterr().out + assert "Set childkey take." in output + + # Test 1: Set multiple children + alice_exec_command( + GetChildKeyTakeCommand, + [ + "stake", + "get_childkey_take", + "--netuid", + "1", + "--hotkey", + str(alice_keypair.ss58_address), + ], + ) + + output = capsys.readouterr().out + assert f"The childkey take for {alice_keypair.ss58_address} is \n11.999694819562066%" in output + + +@pytest.mark.asyncio +async def test_set_revoke_children_singular(local_chain, capsys): + """ + Test the setting and revoking of children hotkeys for staking. + + This test case covers the following scenarios: + 1. Setting multiple children hotkeys with specified proportions (set one at a time) + 2. Retrieving children information + 3. Revoking children hotkeys (one at a time) + 4. Verifying the absence of children after revocation + + The test uses three wallets (Alice, Bob, and Eve) and performs operations + on a local blockchain. + + Args: + local_chain: A fixture providing access to the local blockchain + capsys: A pytest fixture for capturing stdout and stderr + + The test performs the following steps: + - Set up wallets for Alice, Bob, and Eve + - Create a subnet and register wallets + - Add stake to Alice's wallet + - Set Bob and Eve as children of Alice with specific proportions + - Verify the children are set correctly + - Get and verify children information + - Revoke all children + - Verify children are revoked + - Check that no children exist after revocation + + This test ensures the proper functioning of setting children hotkeys, + retrieving children information, and revoking children in the staking system. + """ # Setup alice_keypair, alice_exec_command, alice_wallet = setup_wallet("//Alice") bob_keypair, bob_exec_command, bob_wallet = setup_wallet("//Bob") @@ -187,34 +294,71 @@ async def test_set_revoke_childkey_take(local_chain, capsys): for exec_command in [alice_exec_command, bob_exec_command, eve_exec_command]: exec_command(RegisterCommand, ["s", "register", "--netuid", "1"]) + alice_exec_command(StakeCommand, ["stake", "add", "--amount", "100000"]) + async def wait(): # wait rate limit, until we are allowed to get children rate_limit = ( - subtensor.query_constant( - module_name="SubtensorModule", constant_name="InitialTempo" - ).value - * 2 + subtensor.query_constant( + module_name="SubtensorModule", constant_name="InitialTempo" + ).value + * 2 ) curr_block = subtensor.get_current_block() await wait_interval(rate_limit + curr_block + 1, subtensor) subtensor = bittensor.subtensor(network="ws://localhost:9945") - # await wait() + await wait() - # Test 1: Set multiple children + children_with_proportions = [ + [0.6, bob_keypair.ss58_address], + [0.4, eve_keypair.ss58_address], + ] + + # Test 1: Set first children alice_exec_command( - SetChildKeyTakeCommand, + SetChildrenCommand, [ "stake", - "set_childkey_take", + "set_children", "--netuid", "1", + "--children", + f"{children_with_proportions[0][1]}", "--hotkey", str(alice_keypair.ss58_address), - "--take", - "0.12", + "--proportions", + f"{children_with_proportions[0][0]}", + "--wait_for_inclusion", + "True", + "--wait_for_finalization", + "True", + ], + ) + + await wait() + + subtensor = bittensor.subtensor(network="ws://localhost:9945") + children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) + + assert len(children_info) == 1, "Failed to set child hotkeys" + + # Test 2: Set second child + alice_exec_command( + SetChildrenCommand, + [ + "stake", + "set_children", + "--netuid", + "1", + "--children", + f"{children_with_proportions[1][1]}", + "--hotkey", + str(alice_keypair.ss58_address), + "--proportions", + f"{children_with_proportions[1][0]}", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -225,3 +369,94 @@ async def wait(): await wait() subtensor = bittensor.subtensor(network="ws://localhost:9945") + children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) + + assert len(children_info) == 2, "Failed to set child hotkey" + + normalized_proportions = prepare_child_proportions(children_with_proportions) + assert ( + children_info[0][0] == normalized_proportions[1][0] + and children_info[1][0] == normalized_proportions[0][0] + ), "Incorrect proportions set" + + # Test 2: Get children information + alice_exec_command( + GetChildrenCommand, + [ + "stake", + "get_children", + "--netuid", + "1", + "--hotkey", + str(alice_keypair.ss58_address), + ], + ) + output = capsys.readouterr().out + assert "ChildHotkey" in output + assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92Uh… │ 60.0%" in output + assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcC… │ 40.0%" in output + assert "Total │ 100.0%" in output + + await wait() + + # Test 3: Revoke 1 child + alice_exec_command( + RevokeChildrenCommand, + [ + "stake", + "revoke_children", + "--netuid", + "1", + "--hotkey", + str(alice_keypair.ss58_address), + "--children", + f"{children_with_proportions[0][1]}", + "--wait_for_inclusion", + "True", + "--wait_for_finalization", + "True", + ], + ) + await wait() + + subtensor = bittensor.subtensor(network="ws://localhost:9945") + children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) + assert len(children_info) == 1, "Failed to revoke child hotkey" + + # Test 4: Revoke second child + alice_exec_command( + RevokeChildrenCommand, + [ + "stake", + "revoke_children", + "--netuid", + "1", + "--hotkey", + str(alice_keypair.ss58_address), + "--children", + f"{children_with_proportions[1][1]}", + "--wait_for_inclusion", + "True", + "--wait_for_finalization", + "True", + ], + ) + await wait() + subtensor = bittensor.subtensor(network="ws://localhost:9945") + children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) + assert len(children_info) == 0, "Failed to revoke child hotkey" + # Test 4: Get children after revocation + alice_exec_command( + GetChildrenCommand, + [ + "stake", + "get_children", + "--netuid", + "1", + "--hotkey", + str(alice_keypair.ss58_address), + ], + ) + output = capsys.readouterr().out + assert "There are currently no child hotkeys on subnet" in output + From 9839740803169d5d4cc043770cddc9842948d666 Mon Sep 17 00:00:00 2001 From: opendansor Date: Thu, 22 Aug 2024 17:48:46 -0700 Subject: [PATCH 06/27] Child Hotkey refactor --- tests/e2e_tests/Dockerfile | 44 ------------------- .../stake/neurons/templates repository | 1 - 2 files changed, 45 deletions(-) delete mode 100644 tests/e2e_tests/Dockerfile delete mode 160000 tests/e2e_tests/subcommands/stake/neurons/templates repository diff --git a/tests/e2e_tests/Dockerfile b/tests/e2e_tests/Dockerfile deleted file mode 100644 index bbc5e6f2c..000000000 --- a/tests/e2e_tests/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -FROM ubuntu:latest - -# Install Git -RUN apt-get update && \ - apt-get install -y git - -# Install Python and dependencies -RUN apt-get install -y python3 python3-pip python3-venv curl clang libssl-dev llvm libudev-dev protobuf-compiler - -# Install Rustup non-interactively -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path - -# Set environment variables -ENV PATH="/root/.cargo/bin:${PATH}" -ENV CARGO_TERM_COLOR always -ENV RUST_BACKTRACE full - -# Install Rust toolchain and components -RUN /root/.cargo/bin/rustup toolchain install nightly-2024-03-05 && \ - /root/.cargo/bin/rustup target add wasm32-unknown-unknown --toolchain nightly-2024-03-05 && \ - /root/.cargo/bin/rustup component add rust-src --toolchain nightly-2024-03-05 - -# Create a virtual environment for Python packages -RUN python3 -m venv /opt/venv - -# Activate virtual environment and install pytest -COPY ../requirements/prod.txt /app/prod.txt -COPY ../requirements/dev.txt /app/dev.txt -RUN /opt/venv/bin/pip install --upgrade pip -RUN pip3 install -r /app/prod.txt -RUN pip3 install -r /app/dev.txt - -# Set environment variables to use the virtual environment -ENV PATH="/opt/venv/bin:$PATH" - -# Set up your repository and clone subtensor repo -WORKDIR /app -COPY . /app -RUN git clone https://github.com/opentensor/subtensor.git - -# Add any additional setup steps here - -# Set the entry point for running tests -ENTRYPOINT ["pytest", "."] diff --git a/tests/e2e_tests/subcommands/stake/neurons/templates repository b/tests/e2e_tests/subcommands/stake/neurons/templates repository deleted file mode 160000 index dcaeb7ae0..000000000 --- a/tests/e2e_tests/subcommands/stake/neurons/templates repository +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dcaeb7ae0e405a05388667a1aa7b0a053ae338a1 From 897dc5d0cb0dbb5b99b345356c562c1818beef2a Mon Sep 17 00:00:00 2001 From: opendansor Date: Thu, 22 Aug 2024 17:49:10 -0700 Subject: [PATCH 07/27] Child Hotkey refactor --- tests/e2e_tests/multistep/neurons/templates repository | 1 - 1 file changed, 1 deletion(-) delete mode 160000 tests/e2e_tests/multistep/neurons/templates repository diff --git a/tests/e2e_tests/multistep/neurons/templates repository b/tests/e2e_tests/multistep/neurons/templates repository deleted file mode 160000 index dcaeb7ae0..000000000 --- a/tests/e2e_tests/multistep/neurons/templates repository +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dcaeb7ae0e405a05388667a1aa7b0a053ae338a1 From f8531e238afd607614ead0fdf77ceee330aad095 Mon Sep 17 00:00:00 2001 From: opendansor Date: Mon, 26 Aug 2024 09:52:56 -0700 Subject: [PATCH 08/27] Child Hotkey refactor --- bittensor/commands/stake.py | 16 ++++++++++------ bittensor/commands/unstake.py | 2 +- bittensor/subtensor.py | 4 ++-- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 71530673b..430427368 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -890,11 +890,14 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): if curr_children: # print the table of current children - GetChildrenCommand.retrieve_children( + hotkey_stake = subtensor.get_total_stake_for_hotkey(cli.config.hotkey) + GetChildrenCommand.render_table( subtensor=subtensor, hotkey=cli.config.hotkey, - netuid=cli.config.netuid, - render_table=True, + hotkey_stake=hotkey_stake, + children=curr_children, + netuid=netuid, + prompt=False, ) # get new children @@ -905,7 +908,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): proposed_children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] # Set max 5 children - if len(proposed_children) + len(curr_children) > 5: + if curr_children and len(proposed_children) + len(curr_children) > 5: console.print( ":cross_mark:[red] Too many children. Maximum 5 children per hotkey[/red]" ) @@ -1095,9 +1098,9 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): if not cli.config.is_set("hotkey"): cli.config.hotkey = Prompt.ask("Enter parent hotkey (ss58)") hotkey = cli.config.hotkey - if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + if not wallet_utils.is_valid_ss58_address(hotkey): console.print( - f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" ) return @@ -1236,6 +1239,7 @@ def render_table( ) or Balance(0) child_take = subtensor.get_childkey_take(child_hotkey, netuid) + child_take = u16_to_float(child_take) # add to totals total_stake += child_stake.tao diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index efae152cf..6349513ae 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -454,4 +454,4 @@ def add_args(parser: argparse.ArgumentParser): help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) + bittensor.subtensor.add_args(parser) \ No newline at end of file diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index e0d98387f..7f8b62d35 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -4684,7 +4684,7 @@ def make_substrate_call_with_retry(encoded_coldkey_: List[int]): # Child Hotkey Information # ############################ - def get_childkey_take(self, hotkey: str, netuid: int, block: Optional[int] = None) -> Optional[float]: + def get_childkey_take(self, hotkey: str, netuid: int, block: Optional[int] = None) -> Optional[int]: """ Get the childkey take of a hotkey on a specific network. Args: @@ -4702,7 +4702,7 @@ def get_childkey_take(self, hotkey: str, netuid: int, block: Optional[int] = Non params=[hotkey, netuid], ) if childkey_take: - return float(childkey_take.value) + return int(childkey_take.value) except SubstrateRequestException as e: print(f"Error querying ChildKeys: {e}") From 95a124a8758eaa6313a7034fc3d370a419619011 Mon Sep 17 00:00:00 2001 From: opendansor Date: Mon, 26 Aug 2024 13:27:14 -0700 Subject: [PATCH 09/27] Child Hotkey refactor --- bittensor/commands/stake.py | 117 +++++++++++++++++++------------- bittensor/commands/unstake.py | 59 +++++----------- bittensor/extrinsics/staking.py | 15 ++-- 3 files changed, 95 insertions(+), 96 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 430427368..721a908a6 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -618,11 +618,18 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print("Netuid is outside the current subnet range") return - if not cli.config.is_set("hotkey"): - cli.config.hotkey = Prompt.ask("Enter child hotkey (ss58)") - if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + # get parent hotkey + if wallet and wallet.hotkey: + hotkey = wallet.hotkey.ss58_address + console.print(f"Hotkey is {hotkey}") + elif cli.config.is_set("hotkey"): + hotkey = cli.config.hotkey + else: + hotkey = Prompt.ask("Enter child hotkey (ss58)") + + if not wallet_utils.is_valid_ss58_address(hotkey): console.print( - f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" ) return @@ -646,7 +653,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): success, message = subtensor.set_childkey_take( wallet=wallet, netuid=netuid, - hotkey=cli.config.hotkey, + hotkey=hotkey, take=take, wait_for_inclusion=cli.config.wait_for_inclusion, wait_for_finalization=cli.config.wait_for_finalization, @@ -741,6 +748,7 @@ def run(cli: "bittensor.cli"): @staticmethod def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console = Console() + wallet = bittensor.wallet(config=cli.config) # Get values if not set. if not cli.config.is_set("netuid"): @@ -752,24 +760,31 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print("Netuid is outside the current subnet range") return - if not cli.config.is_set("hotkey"): - cli.config.hotkey = Prompt.ask("Enter child hotkey (ss58)") - if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + # get parent hotkey + if wallet and wallet.hotkey: + hotkey = wallet.hotkey.ss58_address + console.print(f"Hotkey is {hotkey}") + elif cli.config.is_set("hotkey"): + hotkey = cli.config.hotkey + else: + hotkey = Prompt.ask("Enter child hotkey (ss58)") + + if not wallet_utils.is_valid_ss58_address(hotkey): console.print( - f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" ) return take_u16 = subtensor.get_childkey_take( netuid=netuid, - hotkey=cli.config.hotkey, + hotkey=hotkey, ) # Result if take_u16: take = u16_to_float(take_u16) console.print( - f"The childkey take for {cli.config.hotkey} is {take * 100}%." + f"The childkey take for {hotkey} is {take * 100}%." ) else: console.print( @@ -872,28 +887,33 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): return # get parent hotkey - if not cli.config.is_set("hotkey"): - cli.config.hotkey = Prompt.ask("Enter parent hotkey (ss58)") - if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + if wallet and wallet.hotkey: + hotkey = wallet.hotkey.ss58_address + elif cli.config.is_set("hotkey"): + hotkey = cli.config.hotkey + else: + hotkey = Prompt.ask("Enter parent hotkey (ss58)") + + if not wallet_utils.is_valid_ss58_address(hotkey): console.print( - f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" ) return # get current children curr_children = GetChildrenCommand.retrieve_children( subtensor=subtensor, - hotkey=cli.config.hotkey, - netuid=cli.config.netuid, + hotkey=hotkey, + netuid=netuid, render_table=False, ) if curr_children: # print the table of current children - hotkey_stake = subtensor.get_total_stake_for_hotkey(cli.config.hotkey) + hotkey_stake = subtensor.get_total_stake_for_hotkey(hotkey) GetChildrenCommand.render_table( subtensor=subtensor, - hotkey=cli.config.hotkey, + hotkey=hotkey, hotkey_stake=hotkey_stake, children=curr_children, netuid=netuid, @@ -903,12 +923,12 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # get new children if not cli.config.is_set("children"): cli.config.children = Prompt.ask( - "Enter child(ren) hotkeys (ss58) as comma-separated values" + "Enter child hotkeys (ss58) as comma-separated values" ) proposed_children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] # Set max 5 children - if curr_children and len(proposed_children) + len(curr_children) > 5: + if len(proposed_children) > 5: console.print( ":cross_mark:[red] Too many children. Maximum 5 children per hotkey[/red]" ) @@ -928,7 +948,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # extract proportions and child addresses from cli input proportions = [float(x) for x in re.split(r"[ ,]+", str(cli.config.proportions))] - total_proposed = sum(proportions) + SetChildrenCommand.get_current_proportion(curr_children) + total_proposed = sum(proportions) if total_proposed > 1: console.print( f":cross_mark:[red]Invalid proportion: The sum of all proportions must be less or equal to than 1 (representing 100% of the allocation). Proposed sum addition is proportions is {total_proposed}.[/red]") @@ -941,15 +961,14 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # combine proposed and current children children_with_proportions = list(zip(proportions, proposed_children)) - children_with_proportions += [(u64_to_float(child[0]), child[1]) for child in curr_children] SetChildrenCommand.print_current_stake(subtensor=subtensor, children=proposed_children, - hotkey=cli.config.hotkey) + hotkey=hotkey) success, message = subtensor.set_children( wallet=wallet, netuid=netuid, - hotkey=cli.config.hotkey, + hotkey=hotkey, children_with_proportions=children_with_proportions, wait_for_inclusion=cli.config.wait_for_inclusion, wait_for_finalization=cli.config.wait_for_finalization, @@ -959,10 +978,11 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Result if success: if cli.config.wait_for_finalization and cli.config.wait_for_inclusion: + console.print("New Status:") GetChildrenCommand.retrieve_children( subtensor=subtensor, - hotkey=cli.config.hotkey, - netuid=cli.config.netuid, + hotkey=hotkey, + netuid=netuid, render_table=True, ) console.print( @@ -1038,12 +1058,6 @@ def print_current_stake(subtensor, children, hotkey): child_stake = subtensor.get_total_stake_for_hotkey(child) console.print(f"Child Hotkey: {child} | Current Child Stake: {child_stake}τ") - console.print("Current Status:") - - @staticmethod - def get_current_proportion(children: List[Tuple[int, str]]) -> float: - return u64_to_float(sum([child[0] for child in children])) - class GetChildrenCommand: """ @@ -1084,8 +1098,10 @@ def run(cli: "bittensor.cli"): @staticmethod def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - # Get values if not set. console = Console() + wallet = bittensor.wallet(config=cli.config) + + # set netuid if not cli.config.is_set("netuid"): cli.config.netuid = int(Prompt.ask("Enter netuid")) netuid = cli.config.netuid @@ -1094,10 +1110,15 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print("Netuid is outside the current subnet range") return - # Get values if not set. - if not cli.config.is_set("hotkey"): - cli.config.hotkey = Prompt.ask("Enter parent hotkey (ss58)") - hotkey = cli.config.hotkey + # get parent hotkey + if wallet and wallet.hotkey: + hotkey = wallet.hotkey.ss58_address + console.print(f"Hotkey is {hotkey}") + elif cli.config.is_set("hotkey"): + hotkey = cli.config.hotkey + else: + hotkey = Prompt.ask("Enter parent hotkey (ss58)") + if not wallet_utils.is_valid_ss58_address(hotkey): console.print( f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" @@ -1118,18 +1139,18 @@ def retrieve_children( subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool ) -> list[tuple[int, str]]: """ - + Static method to retrieve children for a given subtensor. - + Args: subtensor (bittensor.subtensor): The subtensor object used to interact with the Bittensor network. hotkey (str): The hotkey of the parent. netuid (int): The network unique identifier of the subtensor. render_table (bool): Flag indicating whether to render the retrieved children in a table. - + Returns: List[str]: A list of children hotkeys. - + """ children = subtensor.get_children(hotkey, netuid) if render_table: @@ -1261,10 +1282,10 @@ def render_table( total_proportion += proportion_percent # Conditionally format text - proportion_str = f"{proportion_percent}% ({proportion_tao}τ)" + proportion_str = f"{proportion_percent:.3f}% ({proportion_tao:.3f}τ)" stake_weight = stake.tao + proportion_tao total_stake_weight += stake_weight - take_str = f"{child_take * 100}%" + take_str = f"{child_take * 100:.3f}%" hotkey = Text(hotkey, style="red" if proportion == 0 else "") table.add_row( @@ -1272,7 +1293,7 @@ def render_table( hotkey, proportion_str, take_str, - str(stake_weight), + str(f"{stake_weight:.3f}"), ) avg_take = avg_take / len(children_info) @@ -1281,8 +1302,8 @@ def render_table( table.add_row( "", "Total", - f"{total_proportion}%", - f"{avg_take * 100}%", - f"{total_stake_weight}τ", + f"{total_proportion:.3f}%", + f"(avg) {avg_take * 100:.3f}%", + f"{total_stake_weight:.3f}τ", ) console.print(table) diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index 6349513ae..171f214cd 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -18,7 +18,6 @@ import sys import argparse from typing import List, Union, Optional, Tuple -import re from rich.prompt import Confirm, Prompt from tqdm import tqdm @@ -28,7 +27,6 @@ from . import defaults, GetChildrenCommand from .utils import get_hotkey_wallets_for_wallet from ..utils import wallet_utils -from ..utils.formatting import u64_to_float console = bittensor.__console__ @@ -339,7 +337,7 @@ def run(cli: "bittensor.cli"): @staticmethod def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): wallet = bittensor.wallet(config=cli.config) - + # Get values if not set. if not cli.config.is_set("netuid"): cli.config.netuid = int(Prompt.ask("Enter netuid")) @@ -351,46 +349,25 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): return # get parent hotkey - if not cli.config.is_set("hotkey"): - cli.config.hotkey = Prompt.ask("Enter parent hotkey (ss58)") - if not wallet_utils.is_valid_ss58_address(cli.config.hotkey): + if wallet and wallet.hotkey: + hotkey = wallet.hotkey.ss58_address + console.print(f"Hotkey is {hotkey}") + elif cli.config.is_set("hotkey"): + hotkey = cli.config.hotkey + else: + hotkey = Prompt.ask("Enter parent hotkey (ss58)") + + if not wallet_utils.is_valid_ss58_address(hotkey): console.print( - f":cross_mark:[red] Invalid SS58 address: {cli.config.hotkey}[/red]" + f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" ) return - # Get and display current children information - current_children = GetChildrenCommand.retrieve_children( - subtensor=subtensor, - hotkey=cli.config.hotkey, - netuid=cli.config.netuid, - render_table=True, - ) - - # get new children - if not cli.config.is_set("children"): - cli.config.children = Prompt.ask( - "Enter children hotkeys (ss58) as comma-separated values that you wish to revoke." - ) - revoked_children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] - - # Check if revoked children are in current children - current_children_set = set(child[1] for child in current_children) - for revoked_child in revoked_children: - if revoked_child not in current_children_set: - console.print( - f":cross_mark:[red] Child hotkey {revoked_child} is not present in current children hotkeys.[/red]") - return - - # Prepare children with zero proportions for revoked children and convert proportions of non-revoked children to float - children_with_proportions = [(0, child[1]) if child[1] in revoked_children else (u64_to_float(child[0]), child[1]) for - child in current_children] - success, message = subtensor.set_children( wallet=wallet, netuid=netuid, - children_with_proportions=children_with_proportions, - hotkey=cli.config.hotkey, + children_with_proportions=[], + hotkey=hotkey, wait_for_inclusion=cli.config.wait_for_inclusion, wait_for_finalization=cli.config.wait_for_finalization, prompt=cli.config.prompt, @@ -401,8 +378,8 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): if cli.config.wait_for_finalization and cli.config.wait_for_inclusion: GetChildrenCommand.retrieve_children( subtensor=subtensor, - hotkey=cli.config.hotkey, - netuid=cli.config.netuid, + hotkey=hotkey, + netuid=netuid, render_table=True, ) console.print( @@ -429,9 +406,6 @@ def add_args(parser: argparse.ArgumentParser): ) parser.add_argument("--netuid", dest="netuid", type=int, required=False) parser.add_argument("--hotkey", dest="hotkey", type=str, required=False) - parser.add_argument( - "--children", dest="children", type=str, required=False - ) parser.add_argument( "--wait_for_inclusion", dest="wait_for_inclusion", @@ -454,4 +428,5 @@ def add_args(parser: argparse.ArgumentParser): help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) \ No newline at end of file + bittensor.subtensor.add_args(parser) + \ No newline at end of file diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 8bfd57409..2ef429104 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -670,7 +670,7 @@ def set_children_extrinsic( raise ValueError("Cannot set/revoke child hotkeys for others.") # Check if all children are being revoked - all_revoked = all(prop == 0.0 for prop, _ in children_with_proportions) + all_revoked = len(children_with_proportions) == 0 operation = "Revoke children hotkeys" if all_revoked else "Set children hotkeys" @@ -696,11 +696,14 @@ def set_children_extrinsic( f":satellite: {operation} on [white]{subtensor.network}[/white] ..." ): try: - normalized_children = ( - prepare_child_proportions(children_with_proportions) - if not all_revoked - else children_with_proportions - ) + if not all_revoked: + normalized_children = ( + prepare_child_proportions(children_with_proportions) + if not all_revoked + else children_with_proportions + ) + else: + normalized_children = [] success, error_message = subtensor._do_set_children( wallet=wallet, From 17035e08712404db53b17bcadc47091f016b6313 Mon Sep 17 00:00:00 2001 From: opendansor Date: Mon, 26 Aug 2024 13:39:37 -0700 Subject: [PATCH 10/27] Child Hotkey refactor - fix tests --- .../subcommands/stake/test_childkeys.py | 47 ++++--------------- 1 file changed, 10 insertions(+), 37 deletions(-) diff --git a/tests/e2e_tests/subcommands/stake/test_childkeys.py b/tests/e2e_tests/subcommands/stake/test_childkeys.py index eeaba442a..dad658adc 100644 --- a/tests/e2e_tests/subcommands/stake/test_childkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_childkeys.py @@ -127,9 +127,9 @@ async def wait(): ) output = capsys.readouterr().out assert "ChildHotkey" in output - assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92Uh… │ 40.0%" in output - assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcC… │ 20.0%" in output - assert "Total │ 60.0%" in output + assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92U… │ 40.000%" in output + assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZc… │ 20.000%" in output + assert "Total │ 60.000%" in output await wait() @@ -143,8 +143,6 @@ async def wait(): "1", "--hotkey", str(alice_keypair.ss58_address), - "--children", - f"{children_with_proportions[0][1]},{children_with_proportions[1][1]}", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -337,6 +335,10 @@ async def wait(): "True", ], ) + + output = capsys.readouterr().out + assert "ChildHotkey" in output + assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92U… │ 60.000%" in output await wait() @@ -371,13 +373,7 @@ async def wait(): subtensor = bittensor.subtensor(network="ws://localhost:9945") children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) - assert len(children_info) == 2, "Failed to set child hotkey" - - normalized_proportions = prepare_child_proportions(children_with_proportions) - assert ( - children_info[0][0] == normalized_proportions[1][0] - and children_info[1][0] == normalized_proportions[0][0] - ), "Incorrect proportions set" + assert len(children_info) == 1, "Failed to set child hotkey" # Test 2: Get children information alice_exec_command( @@ -393,31 +389,9 @@ async def wait(): ) output = capsys.readouterr().out assert "ChildHotkey" in output - assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92Uh… │ 60.0%" in output - assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcC… │ 40.0%" in output - assert "Total │ 100.0%" in output + assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZc… │ 40.000%" in output await wait() - - # Test 3: Revoke 1 child - alice_exec_command( - RevokeChildrenCommand, - [ - "stake", - "revoke_children", - "--netuid", - "1", - "--hotkey", - str(alice_keypair.ss58_address), - "--children", - f"{children_with_proportions[0][1]}", - "--wait_for_inclusion", - "True", - "--wait_for_finalization", - "True", - ], - ) - await wait() subtensor = bittensor.subtensor(network="ws://localhost:9945") children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) @@ -433,8 +407,6 @@ async def wait(): "1", "--hotkey", str(alice_keypair.ss58_address), - "--children", - f"{children_with_proportions[1][1]}", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -445,6 +417,7 @@ async def wait(): subtensor = bittensor.subtensor(network="ws://localhost:9945") children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) assert len(children_info) == 0, "Failed to revoke child hotkey" + # Test 4: Get children after revocation alice_exec_command( GetChildrenCommand, From f735b5e8beb7776759be8eceed274d9571e65ff3 Mon Sep 17 00:00:00 2001 From: opendansor Date: Mon, 26 Aug 2024 16:08:35 -0700 Subject: [PATCH 11/27] Child Hotkey refactor - fix tests --- bittensor/commands/stake.py | 2 +- bittensor/commands/unstake.py | 2 +- bittensor/extrinsics/staking.py | 16 ++++++++-------- bittensor/utils/formatting.py | 2 ++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 721a908a6..e4aafe19b 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -1037,7 +1037,7 @@ def add_args(parser: argparse.ArgumentParser): "--prompt", dest="prompt", action="store_true", - default=False, + default=True, help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(set_children_parser) diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index 171f214cd..d19632f43 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -424,7 +424,7 @@ def add_args(parser: argparse.ArgumentParser): "--prompt", dest="prompt", action="store_true", - default=False, + default=True, help="""Prompt for confirmation before proceeding.""", ) bittensor.wallet.add_args(parser) diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 2ef429104..f08343063 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -661,14 +661,6 @@ def set_children_extrinsic( bittensor.errors.NotRegisteredError: If the hotkey is not registered in any subnets. """ - - # Decrypt coldkey. - wallet.coldkey - - user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. - if hotkey != user_hotkey_ss58: - raise ValueError("Cannot set/revoke child hotkeys for others.") - # Check if all children are being revoked all_revoked = len(children_with_proportions) == 0 @@ -692,6 +684,14 @@ def set_children_extrinsic( ): return False, "Operation Cancelled" + + # Decrypt coldkey. + wallet.coldkey + + user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. + if hotkey != user_hotkey_ss58: + raise ValueError("Cannot set/revoke child hotkeys for others.") + with bittensor.__console__.status( f":satellite: {operation} on [white]{subtensor.network}[/white] ..." ): diff --git a/bittensor/utils/formatting.py b/bittensor/utils/formatting.py index 21560ea5f..dde3f521c 100644 --- a/bittensor/utils/formatting.py +++ b/bittensor/utils/formatting.py @@ -39,6 +39,8 @@ def convert_blocks_to_time(blocks: int, block_time: int = 12) -> tuple[int, int, def float_to_u16(value: float) -> int: # Ensure the input is within the expected range + if value is None: + return 0 if not (0 <= value <= 1): raise ValueError("Input value must be between 0 and 1") From 1911056724dbf50976e96caebd431ca2d3f30dfa Mon Sep 17 00:00:00 2001 From: opendansor Date: Mon, 26 Aug 2024 16:11:17 -0700 Subject: [PATCH 12/27] Child Hotkey refactor - fix tests --- bittensor/utils/formatting.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bittensor/utils/formatting.py b/bittensor/utils/formatting.py index dde3f521c..be1daa422 100644 --- a/bittensor/utils/formatting.py +++ b/bittensor/utils/formatting.py @@ -51,6 +51,8 @@ def float_to_u16(value: float) -> int: def u16_to_float(value: int) -> float: # Ensure the input is within the expected range + if value is None: + return 0.0 if not (0 <= value <= 65535): raise ValueError("Input value must be between 0 and 65535") From 204d8f57a937607fdc44f12714336721c18c4c7b Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 10:47:48 -0700 Subject: [PATCH 13/27] Child Hotkey refactor - hotkey or ss58 --- bittensor/commands/stake.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index e4aafe19b..6f0312d11 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -891,6 +891,8 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): hotkey = wallet.hotkey.ss58_address elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey + elif cli.config.is_set("ss58"): + hotkey = cli.config.ss58 else: hotkey = Prompt.ask("Enter parent hotkey (ss58)") @@ -999,8 +1001,11 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) + hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): + config.ss58 = str(hotkey_or_ss58) + else: + config.wallet.hotkey = str(hotkey_or_ss58) @staticmethod def add_args(parser: argparse.ArgumentParser): @@ -1051,7 +1056,7 @@ def print_current_stake(subtensor, children, hotkey): ) console.print("Current Status:") console.print( - f"Parent HotKey: {hotkey} | ", style="cyan", end="", no_wrap=True + f"Parent Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True ) console.print(f"Total Parent Stake: {parent_stake}τ") for child in children: @@ -1113,9 +1118,10 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # get parent hotkey if wallet and wallet.hotkey: hotkey = wallet.hotkey.ss58_address - console.print(f"Hotkey is {hotkey}") elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey + elif cli.config.is_set("ss58"): + hotkey = cli.config.ss58 else: hotkey = Prompt.ask("Enter parent hotkey (ss58)") @@ -1166,8 +1172,11 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) + hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): + config.ss58 = str(hotkey_or_ss58) + else: + config.wallet.hotkey = str(hotkey_or_ss58) @staticmethod def add_args(parser: argparse.ArgumentParser): From fac95af1612d09351b19df343281da619d042f05 Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 12:18:19 -0700 Subject: [PATCH 14/27] Modify table, no prompt, other touch ups --- bittensor/commands/stake.py | 84 +++++++++++++------ bittensor/commands/unstake.py | 12 +++ bittensor/extrinsics/staking.py | 9 +- bittensor/subtensor.py | 2 +- .../subcommands/stake/test_childkeys.py | 52 +++++++++--- 5 files changed, 117 insertions(+), 42 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 6f0312d11..36a435269 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -621,11 +621,12 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # get parent hotkey if wallet and wallet.hotkey: hotkey = wallet.hotkey.ss58_address - console.print(f"Hotkey is {hotkey}") elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey + elif cli.config.is_set("ss58"): + hotkey = cli.config.ss58 else: - hotkey = Prompt.ask("Enter child hotkey (ss58)") + hotkey = Prompt.ask("Enter hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(hotkey): console.print( @@ -665,6 +666,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print( ":white_heavy_check_mark: [green]Set childkey take.[/green]" ) + console.print(f"The childkey take for {hotkey} is now set to {take * 100:.3f}%.") else: console.print( f":cross_mark:[red] Unable to set childkey take.[/red] {message}" @@ -676,8 +678,11 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) + hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): + config.ss58 = str(hotkey_or_ss58) + else: + config.wallet.hotkey = str(hotkey_or_ss58) @staticmethod def add_args(parser: argparse.ArgumentParser): @@ -711,9 +716,21 @@ def add_args(parser: argparse.ArgumentParser): "--prompt", dest="prompt", action="store_true", - default=False, + default=True, help="""Prompt for confirmation before proceeding.""", ) + set_childkey_take_parser.add_argument( + "--no_prompt", + dest="prompt", + action="store_false", + help="""Disable prompt for confirmation before proceeding.""", + ) + set_childkey_take_parser.add_argument( + "--y", + dest="prompt", + action="store_false", + help="""Defaults to Yes for all prompts.""", + ) bittensor.wallet.add_args(set_childkey_take_parser) bittensor.subtensor.add_args(set_childkey_take_parser) @@ -763,11 +780,12 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # get parent hotkey if wallet and wallet.hotkey: hotkey = wallet.hotkey.ss58_address - console.print(f"Hotkey is {hotkey}") elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey + elif cli.config.is_set("ss58"): + hotkey = cli.config.ss58 else: - hotkey = Prompt.ask("Enter child hotkey (ss58)") + hotkey = Prompt.ask("Enter hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(hotkey): console.print( @@ -784,7 +802,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): if take_u16: take = u16_to_float(take_u16) console.print( - f"The childkey take for {hotkey} is {take * 100}%." + f"The childkey take for {hotkey} is {take * 100:.3f}%." ) else: console.print( @@ -797,8 +815,11 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) + hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): + config.ss58 = str(hotkey_or_ss58) + else: + config.wallet.hotkey = str(hotkey_or_ss58) @staticmethod def add_args(parser: argparse.ArgumentParser): @@ -892,7 +913,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey elif cli.config.is_set("ss58"): - hotkey = cli.config.ss58 + hotkey = cli.config.ss58 else: hotkey = Prompt.ask("Enter parent hotkey (ss58)") @@ -1045,6 +1066,18 @@ def add_args(parser: argparse.ArgumentParser): default=True, help="""Prompt for confirmation before proceeding.""", ) + set_children_parser.add_argument( + "--no_prompt", + dest="prompt", + action="store_false", + help="""Disable prompt for confirmation before proceeding.""", + ) + set_children_parser.add_argument( + "--y", + dest="prompt", + action="store_false", + help="""Defaults to Yes for all prompts.""", + ) bittensor.wallet.add_args(set_children_parser) bittensor.subtensor.add_args(set_children_parser) @@ -1229,28 +1262,28 @@ def render_table( table = Table( show_header=True, header_style="bold magenta", - border_style="green", - style="green", + border_style="blue", + style="dim", ) # Add columns to the table with specific styles - table.add_column("Index", style="cyan", no_wrap=True, justify="right") - table.add_column("ChildHotkey", style="cyan", no_wrap=True) - table.add_column("Proportion", style="cyan", no_wrap=True, justify="right") - table.add_column("Childkey Take", style="cyan", no_wrap=True, justify="right") + table.add_column("Index", style="bold yellow", no_wrap=True, justify="center") + table.add_column("ChildHotkey", style="bold green") + table.add_column("Proportion", style="bold cyan", no_wrap=True, justify="right") + table.add_column("Childkey Take", style="bold blue", no_wrap=True, justify="right") table.add_column( - "New Stake Weight", style="cyan", no_wrap=True, justify="right" + "Current Stake Weight", style="bold red", no_wrap=True, justify="right" ) if not children: console.print(table) console.print( - f"There are currently no child hotkeys on subnet {netuid} with Parent HotKey {hotkey}." + f"[bold red]There are currently no child hotkeys on subnet {netuid} with Parent HotKey {hotkey}.[/bold red]" ) if prompt: command = f"btcli stake set_children --children --hotkey --netuid {netuid} --proportion " console.print( - f"To add a child hotkey you can run the command: [white]{command}[/white]" + f"[bold cyan]To add a child hotkey you can run the command: [white]{command}[/white][/bold cyan]" ) return @@ -1296,7 +1329,7 @@ def render_table( total_stake_weight += stake_weight take_str = f"{child_take * 100:.3f}%" - hotkey = Text(hotkey, style="red" if proportion == 0 else "") + hotkey = Text(hotkey, style="italic red" if proportion == 0 else "") table.add_row( str(i), hotkey, @@ -1310,9 +1343,10 @@ def render_table( # add totals row table.add_row( "", - "Total", - f"{total_proportion:.3f}%", - f"(avg) {avg_take * 100:.3f}%", - f"{total_stake_weight:.3f}τ", + "[dim]Total[/dim]", + f"[dim]{total_proportion:.3f}%[/dim]", + f"[dim](avg) {avg_take * 100:.3f}%[/dim]", + f"[dim]{total_stake_weight:.3f}τ[/dim]", + style="dim" ) console.print(table) diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index d19632f43..178637759 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -427,6 +427,18 @@ def add_args(parser: argparse.ArgumentParser): default=True, help="""Prompt for confirmation before proceeding.""", ) + parser.add_argument( + "--no_prompt", + dest="prompt", + action="store_false", + help="""Disable prompt for confirmation before proceeding.""", + ) + parser.add_argument( + "--y", + dest="prompt", + action="store_false", + help="""Defaults to Yes for all prompts.""", + ) bittensor.wallet.add_args(parser) bittensor.subtensor.add_args(parser) \ No newline at end of file diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index f08343063..98f368f1d 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -568,9 +568,6 @@ def set_childkey_take_extrinsic( """ - # Decrypt coldkey. - wallet.coldkey - user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. if hotkey != user_hotkey_ss58: raise ValueError("You can only set childkey take for ss58 hotkey that you own.") @@ -581,6 +578,9 @@ def set_childkey_take_extrinsic( f"Do you want to set childkey take to: [bold white]{take*100}%[/bold white]?" ): return False, "Operation Cancelled" + + # Decrypt coldkey. + wallet.coldkey with bittensor.__console__.status( f":satellite: Setting childkey take on [white]{subtensor.network}[/white] ..." @@ -664,7 +664,7 @@ def set_children_extrinsic( # Check if all children are being revoked all_revoked = len(children_with_proportions) == 0 - operation = "Revoke children hotkeys" if all_revoked else "Set children hotkeys" + operation = "Revoking all child hotkeys" if all_revoked else "Setting child hotkeys" # Ask before moving on. if prompt: @@ -684,7 +684,6 @@ def set_children_extrinsic( ): return False, "Operation Cancelled" - # Decrypt coldkey. wallet.coldkey diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 7f8b62d35..80eb322f5 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -417,7 +417,7 @@ def determine_chain_endpoint_and_network(network: str): elif "127.0.0.1" in network or "localhost" in network: return "local", network else: - return "unknown", network + return "unknown network", network @staticmethod def setup_config(network: str, config: "bittensor.config"): diff --git a/tests/e2e_tests/subcommands/stake/test_childkeys.py b/tests/e2e_tests/subcommands/stake/test_childkeys.py index dad658adc..3cfd42513 100644 --- a/tests/e2e_tests/subcommands/stake/test_childkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_childkeys.py @@ -93,10 +93,15 @@ async def wait(): str(alice_keypair.ss58_address), "--proportions", f"{children_with_proportions[0][0]},{children_with_proportions[1][0]}", + "--wallet.name", + "default", + "--wallet.hotkey", + "default", "--wait_for_inclusion", "True", "--wait_for_finalization", "True", + ], ) @@ -126,10 +131,9 @@ async def wait(): ], ) output = capsys.readouterr().out - assert "ChildHotkey" in output - assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92U… │ 40.000%" in output - assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZc… │ 20.000%" in output - assert "Total │ 60.000%" in output + assert "5FHne… │ 40.000%" in output + assert "5HGjW… │ 20.000%" in output + assert "Total │ 60.000%" in output await wait() @@ -143,6 +147,10 @@ async def wait(): "1", "--hotkey", str(alice_keypair.ss58_address), + "--wallet.name", + "default", + "--wallet.hotkey", + "default", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -220,6 +228,10 @@ async def test_set_revoke_childkey_take(local_chain, capsys): "1", "--hotkey", str(alice_keypair.ss58_address), + "--wallet.name", + "default", + "--wallet.hotkey", + "default", "--take", "0.12", "--wait_for_inclusion", @@ -230,7 +242,7 @@ async def test_set_revoke_childkey_take(local_chain, capsys): ) output = capsys.readouterr().out - assert "Set childkey take." in output + assert "The childkey take for 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY is now \nset to 12.000%." in output # Test 1: Set multiple children alice_exec_command( @@ -246,7 +258,7 @@ async def test_set_revoke_childkey_take(local_chain, capsys): ) output = capsys.readouterr().out - assert f"The childkey take for {alice_keypair.ss58_address} is \n11.999694819562066%" in output + assert "The childkey take for 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY is \n12.000%." in output @pytest.mark.asyncio @@ -329,6 +341,10 @@ async def wait(): str(alice_keypair.ss58_address), "--proportions", f"{children_with_proportions[0][0]}", + "--wallet.name", + "default", + "--wallet.hotkey", + "default", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -337,8 +353,7 @@ async def wait(): ) output = capsys.readouterr().out - assert "ChildHotkey" in output - assert "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92U… │ 60.000%" in output + assert "5FHn… │ 60.000%" in output await wait() @@ -361,6 +376,10 @@ async def wait(): str(alice_keypair.ss58_address), "--proportions", f"{children_with_proportions[1][0]}", + "--wallet.name", + "default", + "--wallet.hotkey", + "default", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -385,11 +404,14 @@ async def wait(): "1", "--hotkey", str(alice_keypair.ss58_address), + "--wallet.name", + "default", + "--wallet.hotkey", + "default", ], ) output = capsys.readouterr().out - assert "ChildHotkey" in output - assert "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZc… │ 40.000%" in output + assert "5HGj… │ 40.000%" in output await wait() @@ -407,6 +429,10 @@ async def wait(): "1", "--hotkey", str(alice_keypair.ss58_address), + "--wallet.name", + "default", + "--wallet.hotkey", + "default", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -428,8 +454,12 @@ async def wait(): "1", "--hotkey", str(alice_keypair.ss58_address), + "--wallet.name", + "default", + "--wallet.hotkey", + "default", ], ) output = capsys.readouterr().out - assert "There are currently no child hotkeys on subnet" in output + assert "There are currently no child hotkeys on subnet 1 with Parent HotKey \n5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY." in output From 0d73eaab4493cf6f6c1c9dfb1c25d3e19e21777e Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 12:23:06 -0700 Subject: [PATCH 15/27] remove HK check --- bittensor/extrinsics/staking.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 98f368f1d..b3b526b8e 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -568,10 +568,6 @@ def set_childkey_take_extrinsic( """ - user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. - if hotkey != user_hotkey_ss58: - raise ValueError("You can only set childkey take for ss58 hotkey that you own.") - # Ask before moving on. if prompt: if not Confirm.ask( From 2da50a13e161c030a71ce3b0b0d08acc34139d25 Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 13:33:57 -0700 Subject: [PATCH 16/27] Update --- bittensor/commands/stake.py | 5 +++++ bittensor/extrinsics/staking.py | 2 +- bittensor/subtensor.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 36a435269..6b1692364 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -1286,6 +1286,11 @@ def render_table( f"[bold cyan]To add a child hotkey you can run the command: [white]{command}[/white][/bold cyan]" ) return + + console.print( + f"Parent Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True + ) + console.print(f"Total Parent Stake: {hotkey_stake}τ") # calculate totals total_proportion = 0 diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index b3b526b8e..6d183ebf0 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -583,7 +583,7 @@ def set_childkey_take_extrinsic( ): try: - if 0 < take < 0.18: + if 0 < take <= 0.18: take_u16 = float_to_u16(take) else: return False, "Invalid take value" diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 80eb322f5..d142f0e8d 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -4693,7 +4693,7 @@ def get_childkey_take(self, hotkey: str, netuid: int, block: Optional[int] = Non - block (Optional[int]): Optional parameter specifying the block number. Defaults to None. Returns: - - Optional[float]: The value of the "ChildkeyTake" if found, or None if any error occurs. + - Optional[int]: The value of the "ChildkeyTake" if found, or None if any error occurs. """ try: childkey_take = self.query_subtensor( From b8a0fd810b412ced12653a14229a55eaa068d97a Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 14:28:15 -0700 Subject: [PATCH 17/27] minor update --- bittensor/cli.py | 3 ++- bittensor/extrinsics/staking.py | 6 +----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/bittensor/cli.py b/bittensor/cli.py index 35ec78ba4..eddc92b14 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -72,7 +72,8 @@ CheckColdKeySwapCommand, SetChildrenCommand, GetChildrenCommand, - RevokeChildrenCommand, SetChildKeyTakeCommand, + RevokeChildrenCommand, + SetChildKeyTakeCommand, ) from .commands.stake import GetChildKeyTakeCommand diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 6d183ebf0..5dcf1ce3d 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -692,11 +692,7 @@ def set_children_extrinsic( ): try: if not all_revoked: - normalized_children = ( - prepare_child_proportions(children_with_proportions) - if not all_revoked - else children_with_proportions - ) + normalized_children = prepare_child_proportions(children_with_proportions) else: normalized_children = [] From e0dfac815a61a8b9ca2fb8675431d8d038373833 Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 14:42:20 -0700 Subject: [PATCH 18/27] ruff --- bittensor/cli.py | 2 +- bittensor/commands/stake.py | 174 +++++++++++++++++--------------- bittensor/commands/unstake.py | 7 +- bittensor/extrinsics/staking.py | 26 +++-- bittensor/subtensor.py | 17 ++-- bittensor/utils/formatting.py | 2 +- 6 files changed, 120 insertions(+), 108 deletions(-) diff --git a/bittensor/cli.py b/bittensor/cli.py index eddc92b14..63473bead 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -72,7 +72,7 @@ CheckColdKeySwapCommand, SetChildrenCommand, GetChildrenCommand, - RevokeChildrenCommand, + RevokeChildrenCommand, SetChildKeyTakeCommand, ) from .commands.stake import GetChildKeyTakeCommand diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 6b1692364..ec94291b1 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -173,7 +173,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # If the max_stake is greater than the current wallet balance, stake the entire balance. stake_amount_tao: float = min(stake_amount_tao, wallet_balance.tao) if ( - stake_amount_tao <= 0.00001 + stake_amount_tao <= 0.00001 ): # Threshold because of fees, might create a loop otherwise # Skip hotkey if max_stake is less than current stake. continue @@ -196,13 +196,13 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Ask to stake if not config.no_prompt: if not Confirm.ask( - f"Do you want to stake to the following keys from {wallet.name}:\n" - + "".join( - [ - f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" - for hotkey, amount in zip(final_hotkeys, final_amounts) - ] - ) + f"Do you want to stake to the following keys from {wallet.name}:\n" + + "".join( + [ + f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" + for hotkey, amount in zip(final_hotkeys, final_amounts) + ] + ) ): return None @@ -231,24 +231,24 @@ def check_config(cls, config: "bittensor.config"): config.wallet.name = str(wallet_name) if ( - not config.is_set("wallet.hotkey") - and not config.no_prompt - and not config.wallet.get("all_hotkeys") - and not config.wallet.get("hotkeys") + not config.is_set("wallet.hotkey") + and not config.no_prompt + and not config.wallet.get("all_hotkeys") + and not config.wallet.get("hotkeys") ): hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) # Get amount. if ( - not config.get("amount") - and not config.get("stake_all") - and not config.get("max_stake") + not config.get("amount") + and not config.get("stake_all") + and not config.get("max_stake") ): if not Confirm.ask( - "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", defaults.wallet.name) - ) + "Stake all Tao from account: [bold]'{}'[/bold]?".format( + config.wallet.get("name", defaults.wallet.name) + ) ): amount = Prompt.ask("Enter Tao amount to stake") try: @@ -327,8 +327,8 @@ def _get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: path=wallet.path, name=wallet.name, hotkey=hotkey_file_name ) if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() + hotkey_for_name.hotkey_file.exists_on_device() + and not hotkey_for_name.hotkey_file.is_encrypted() ): hotkey_wallets.append(hotkey_for_name) except Exception: @@ -391,7 +391,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) def get_stake_accounts( - wallet, subtensor + wallet, subtensor ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Get stake account details for the given wallet. @@ -420,7 +420,7 @@ def get_stake_accounts( } def get_stakes_from_hotkeys( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from hotkeys for the provided wallet. @@ -437,8 +437,8 @@ def get_stakes_from_hotkeys( [ n.emission for n in subtensor.get_all_neurons_for_pubkey( - hot.hotkey.ss58_address - ) + hot.hotkey.ss58_address + ) ] ) hotkey_stake = subtensor.get_stake_for_coldkey_and_hotkey( @@ -453,7 +453,7 @@ def get_stakes_from_hotkeys( return stakes def get_stakes_from_delegates( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from delegates for the provided wallet. @@ -479,13 +479,13 @@ def get_stakes_from_delegates( "name": delegate_name, "stake": nom[1], "rate": dele.total_daily_return.tao - * (nom[1] / dele.total_stake.tao), + * (nom[1] / dele.total_stake.tao), } return stakes def get_all_wallet_accounts( - wallets, - subtensor, + wallets, + subtensor, ) -> List[Dict[str, Dict[str, Union[str, Balance]]]]: """Fetch stake accounts for all provided wallets using a ThreadPool. @@ -550,9 +550,9 @@ def get_all_wallet_accounts( @staticmethod def check_config(config: "bittensor.config"): if ( - not config.get("all", d=None) - and not config.is_set("wallet.name") - and not config.no_prompt + not config.get("all", d=None) + and not config.is_set("wallet.name") + and not config.no_prompt ): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) @@ -577,7 +577,7 @@ class SetChildKeyTakeCommand: """ Executes the ``set_childkey_take`` command to modify your childkey take on a specified subnet on the Bittensor network to the caller. - This command is used to modify your childkey take on a specified subnet on the Bittensor network. + This command is used to modify your childkey take on a specified subnet on the Bittensor network. Usage: Users can specify the amount or 'take' for their child hotkeys (``SS58`` address), @@ -624,14 +624,12 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey elif cli.config.is_set("ss58"): - hotkey = cli.config.ss58 + hotkey = cli.config.ss58 else: hotkey = Prompt.ask("Enter hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(hotkey): - console.print( - f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" - ) + console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return if not cli.config.is_set("take"): @@ -643,12 +641,15 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): try: take = float(cli.config.take) except ValueError: - print(":cross_mark:[red]Take must be a float value using characters between 0 and 9.[/red]") + print( + ":cross_mark:[red]Take must be a float value using characters between 0 and 9.[/red]" + ) return if take < 0 or take > 0.18: console.print( - f":cross_mark:[red]Invalid take: Childkey Take must be between 0 and 0.18 (representing 0% to 18%). Proposed take is {take}.[/red]") + f":cross_mark:[red]Invalid take: Childkey Take must be between 0 and 0.18 (representing 0% to 18%). Proposed take is {take}.[/red]" + ) return success, message = subtensor.set_childkey_take( @@ -663,10 +664,10 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Result if success: + console.print(":white_heavy_check_mark: [green]Set childkey take.[/green]") console.print( - ":white_heavy_check_mark: [green]Set childkey take.[/green]" + f"The childkey take for {hotkey} is now set to {take * 100:.3f}%." ) - console.print(f"The childkey take for {hotkey} is now set to {take * 100:.3f}%.") else: console.print( f":cross_mark:[red] Unable to set childkey take.[/red] {message}" @@ -678,7 +679,9 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + hotkey_or_ss58 = Prompt.ask( + "Enter hotkey name or ss58", default=defaults.wallet.hotkey + ) if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): config.ss58 = str(hotkey_or_ss58) else: @@ -739,7 +742,7 @@ class GetChildKeyTakeCommand: """ Executes the ``get_childkey_take`` command to get your childkey take on a specified subnet on the Bittensor network to the caller. - This command is used to get your childkey take on a specified subnet on the Bittensor network. + This command is used to get your childkey take on a specified subnet on the Bittensor network. Usage: Users can get the amount or 'take' for their child hotkeys (``SS58`` address) @@ -783,14 +786,12 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey elif cli.config.is_set("ss58"): - hotkey = cli.config.ss58 + hotkey = cli.config.ss58 else: hotkey = Prompt.ask("Enter hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(hotkey): - console.print( - f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" - ) + console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return take_u16 = subtensor.get_childkey_take( @@ -801,13 +802,9 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Result if take_u16: take = u16_to_float(take_u16) - console.print( - f"The childkey take for {hotkey} is {take * 100:.3f}%." - ) + console.print(f"The childkey take for {hotkey} is {take * 100:.3f}%.") else: - console.print( - ":cross_mark:[red] Unable to get childkey take.[/red]" - ) + console.print(":cross_mark:[red] Unable to get childkey take.[/red]") @staticmethod def check_config(config: "bittensor.config"): @@ -815,7 +812,9 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + hotkey_or_ss58 = Prompt.ask( + "Enter hotkey name or ss58", default=defaults.wallet.hotkey + ) if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): config.ss58 = str(hotkey_or_ss58) else: @@ -918,9 +917,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): hotkey = Prompt.ask("Enter parent hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(hotkey): - console.print( - f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" - ) + console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return # get current children @@ -970,23 +967,28 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) # extract proportions and child addresses from cli input - proportions = [float(x) for x in re.split(r"[ ,]+", str(cli.config.proportions))] + proportions = [ + float(x) for x in re.split(r"[ ,]+", str(cli.config.proportions)) + ] total_proposed = sum(proportions) if total_proposed > 1: console.print( - f":cross_mark:[red]Invalid proportion: The sum of all proportions must be less or equal to than 1 (representing 100% of the allocation). Proposed sum addition is proportions is {total_proposed}.[/red]") + f":cross_mark:[red]Invalid proportion: The sum of all proportions must be less or equal to than 1 (representing 100% of the allocation). Proposed sum addition is proportions is {total_proposed}.[/red]" + ) return if len(proportions) != len(proposed_children): console.print( - ":cross_mark:[red]Invalid proportion and children length: The count of children and number of proportion values entered do not match.[/red]") + ":cross_mark:[red]Invalid proportion and children length: The count of children and number of proportion values entered do not match.[/red]" + ) return # combine proposed and current children children_with_proportions = list(zip(proportions, proposed_children)) - SetChildrenCommand.print_current_stake(subtensor=subtensor, children=proposed_children, - hotkey=hotkey) + SetChildrenCommand.print_current_stake( + subtensor=subtensor, children=proposed_children, hotkey=hotkey + ) success, message = subtensor.set_children( wallet=wallet, @@ -1022,7 +1024,9 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + hotkey_or_ss58 = Prompt.ask( + "Enter hotkey name or ss58", default=defaults.wallet.hotkey + ) if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): config.ss58 = str(hotkey_or_ss58) else: @@ -1084,9 +1088,7 @@ def add_args(parser: argparse.ArgumentParser): @staticmethod def print_current_stake(subtensor, children, hotkey): console = Console() - parent_stake = subtensor.get_total_stake_for_hotkey( - ss58_address=hotkey - ) + parent_stake = subtensor.get_total_stake_for_hotkey(ss58_address=hotkey) console.print("Current Status:") console.print( f"Parent Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True @@ -1094,7 +1096,9 @@ def print_current_stake(subtensor, children, hotkey): console.print(f"Total Parent Stake: {parent_stake}τ") for child in children: child_stake = subtensor.get_total_stake_for_hotkey(child) - console.print(f"Child Hotkey: {child} | Current Child Stake: {child_stake}τ") + console.print( + f"Child Hotkey: {child} | Current Child Stake: {child_stake}τ" + ) class GetChildrenCommand: @@ -1159,9 +1163,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): hotkey = Prompt.ask("Enter parent hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(hotkey): - console.print( - f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" - ) + console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return children = subtensor.get_children(hotkey, netuid) @@ -1175,21 +1177,21 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): @staticmethod def retrieve_children( - subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool + subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool ) -> list[tuple[int, str]]: """ - + Static method to retrieve children for a given subtensor. - + Args: subtensor (bittensor.subtensor): The subtensor object used to interact with the Bittensor network. hotkey (str): The hotkey of the parent. netuid (int): The network unique identifier of the subtensor. render_table (bool): Flag indicating whether to render the retrieved children in a table. - + Returns: List[str]: A list of children hotkeys. - + """ children = subtensor.get_children(hotkey, netuid) if render_table: @@ -1205,7 +1207,9 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey_or_ss58 = Prompt.ask("Enter hotkey name or ss58", default=defaults.wallet.hotkey) + hotkey_or_ss58 = Prompt.ask( + "Enter hotkey name or ss58", default=defaults.wallet.hotkey + ) if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): config.ss58 = str(hotkey_or_ss58) else: @@ -1224,12 +1228,12 @@ def add_args(parser: argparse.ArgumentParser): @staticmethod def render_table( - subtensor: "bittensor.subtensor", - hotkey: str, - hotkey_stake: "Balance", - children: list[Tuple[int, str]], - netuid: int, - prompt: bool, + subtensor: "bittensor.subtensor", + hotkey: str, + hotkey_stake: "Balance", + children: list[Tuple[int, str]], + netuid: int, + prompt: bool, ): """ @@ -1270,7 +1274,9 @@ def render_table( table.add_column("Index", style="bold yellow", no_wrap=True, justify="center") table.add_column("ChildHotkey", style="bold green") table.add_column("Proportion", style="bold cyan", no_wrap=True, justify="right") - table.add_column("Childkey Take", style="bold blue", no_wrap=True, justify="right") + table.add_column( + "Childkey Take", style="bold blue", no_wrap=True, justify="right" + ) table.add_column( "Current Stake Weight", style="bold red", no_wrap=True, justify="right" ) @@ -1286,7 +1292,7 @@ def render_table( f"[bold cyan]To add a child hotkey you can run the command: [white]{command}[/white][/bold cyan]" ) return - + console.print( f"Parent Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True ) @@ -1352,6 +1358,6 @@ def render_table( f"[dim]{total_proportion:.3f}%[/dim]", f"[dim](avg) {avg_take * 100:.3f}%[/dim]", f"[dim]{total_stake_weight:.3f}τ[/dim]", - style="dim" + style="dim", ) console.print(table) diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index 178637759..89fc6e77a 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -337,7 +337,7 @@ def run(cli: "bittensor.cli"): @staticmethod def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): wallet = bittensor.wallet(config=cli.config) - + # Get values if not set. if not cli.config.is_set("netuid"): cli.config.netuid = int(Prompt.ask("Enter netuid")) @@ -358,9 +358,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): hotkey = Prompt.ask("Enter parent hotkey (ss58)") if not wallet_utils.is_valid_ss58_address(hotkey): - console.print( - f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]" - ) + console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return success, message = subtensor.set_children( @@ -441,4 +439,3 @@ def add_args(parser: argparse.ArgumentParser): ) bittensor.wallet.add_args(parser) bittensor.subtensor.add_args(parser) - \ No newline at end of file diff --git a/bittensor/extrinsics/staking.py b/bittensor/extrinsics/staking.py index 5dcf1ce3d..b6d5cf5d6 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/extrinsics/staking.py @@ -574,7 +574,7 @@ def set_childkey_take_extrinsic( f"Do you want to set childkey take to: [bold white]{take*100}%[/bold white]?" ): return False, "Operation Cancelled" - + # Decrypt coldkey. wallet.coldkey @@ -582,7 +582,6 @@ def set_childkey_take_extrinsic( f":satellite: Setting childkey take on [white]{subtensor.network}[/white] ..." ): try: - if 0 < take <= 0.18: take_u16 = float_to_u16(take) else: @@ -686,13 +685,15 @@ def set_children_extrinsic( user_hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. if hotkey != user_hotkey_ss58: raise ValueError("Cannot set/revoke child hotkeys for others.") - + with bittensor.__console__.status( f":satellite: {operation} on [white]{subtensor.network}[/white] ..." ): try: if not all_revoked: - normalized_children = prepare_child_proportions(children_with_proportions) + normalized_children = prepare_child_proportions( + children_with_proportions + ) else: normalized_children = [] @@ -738,17 +739,22 @@ def prepare_child_proportions(children_with_proportions): """ Convert proportions to u64 and normalize, ensuring total does not exceed u64 max. """ - children_u64 = [(float_to_u64(proportion), child) for proportion, child in children_with_proportions] + children_u64 = [ + (float_to_u64(proportion), child) + for proportion, child in children_with_proportions + ] total = sum(proportion for proportion, _ in children_u64) - if total > (2 ** 64 - 1): - excess = total - (2 ** 64 - 1) - if excess > (2 ** 64 * 0.01): # Example threshold of 1% of u64 max + if total > (2**64 - 1): + excess = total - (2**64 - 1) + if excess > (2**64 * 0.01): # Example threshold of 1% of u64 max raise ValueError("Excess is too great to normalize proportions") - largest_child_index = max(range(len(children_u64)), key=lambda i: children_u64[i][0]) + largest_child_index = max( + range(len(children_u64)), key=lambda i: children_u64[i][0] + ) children_u64[largest_child_index] = ( children_u64[largest_child_index][0] - excess, - children_u64[largest_child_index][1] + children_u64[largest_child_index][1], ) return children_u64 diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index d142f0e8d..9326c1425 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -101,7 +101,8 @@ from .extrinsics.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, - set_children_extrinsic, set_childkey_take_extrinsic, + set_children_extrinsic, + set_childkey_take_extrinsic, ) from .extrinsics.transfer import transfer_extrinsic from .extrinsics.unstaking import ( @@ -2300,7 +2301,7 @@ def make_substrate_call_with_retry(): ################### # Child hotkeys # ################### - + def set_childkey_take( self, wallet: "bittensor.wallet", @@ -2337,7 +2338,7 @@ def set_childkey_take( wait_for_finalization=wait_for_finalization, prompt=prompt, ) - + def _do_set_childkey_take( self, wallet: "bittensor.wallet", @@ -2352,7 +2353,7 @@ def _do_set_childkey_take( Args: wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. hotkey: (str): Hotkey ``ss58`` address of the wallet for which take is getting set. - take: (int): The take that this ss58 hotkey will have if assigned as a child hotkey as u16 value. + take: (int): The take that this ss58 hotkey will have if assigned as a child hotkey as u16 value. netuid (int): Unique identifier for the network. wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. wait_for_finalization (bool): If ``true``, waits for finalization before returning. @@ -4684,7 +4685,9 @@ def make_substrate_call_with_retry(encoded_coldkey_: List[int]): # Child Hotkey Information # ############################ - def get_childkey_take(self, hotkey: str, netuid: int, block: Optional[int] = None) -> Optional[int]: + def get_childkey_take( + self, hotkey: str, netuid: int, block: Optional[int] = None + ) -> Optional[int]: """ Get the childkey take of a hotkey on a specific network. Args: @@ -4703,14 +4706,14 @@ def get_childkey_take(self, hotkey: str, netuid: int, block: Optional[int] = Non ) if childkey_take: return int(childkey_take.value) - + except SubstrateRequestException as e: print(f"Error querying ChildKeys: {e}") return None except Exception as e: print(f"Unexpected error in get_children: {e}") return None - + def get_children(self, hotkey, netuid) -> list[tuple[int, str]] | list[Any] | None: """ Get the children of a hotkey on a specific network. diff --git a/bittensor/utils/formatting.py b/bittensor/utils/formatting.py index be1daa422..22fbe74c1 100644 --- a/bittensor/utils/formatting.py +++ b/bittensor/utils/formatting.py @@ -69,7 +69,7 @@ def float_to_u64(value: float) -> int: raise ValueError("Input value must be between 0 and 1") # Convert the float to a u64 value, take the floor value - return int(math.floor((value * (2 ** 64 - 1)))) + return int(math.floor((value * (2**64 - 1)))) def u64_to_float(value: int) -> float: From 658cbb8b801df56f8c04da6ebe23e934b654b235 Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 14:43:14 -0700 Subject: [PATCH 19/27] ruff --- bittensor/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/cli.py b/bittensor/cli.py index 63473bead..cc87c122e 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -74,8 +74,8 @@ GetChildrenCommand, RevokeChildrenCommand, SetChildKeyTakeCommand, + GetChildKeyTakeCommand, ) -from .commands.stake import GetChildKeyTakeCommand # Create a console instance for CLI display. console = bittensor.__console__ From e29f2c37164bc5ea93f13eb4e3038534fdd6ead5 Mon Sep 17 00:00:00 2001 From: opendansor Date: Tue, 27 Aug 2024 14:46:08 -0700 Subject: [PATCH 20/27] ruff --- tests/e2e_tests/conftest.py | 7 +-- .../subcommands/stake/test_childkeys.py | 57 +++++++++++-------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 923d79ce8..6f648be13 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -89,17 +89,16 @@ def wait_for_node_start(process, pattern): if not already_running: # Terminate the process group (includes all child processes) os.killpg(os.getpgid(process.pid), signal.SIGTERM) - + # Give some time for the process to terminate time.sleep(1) - + # If the process is not terminated, send SIGKILL if process.poll() is None: os.killpg(os.getpgid(process.pid), signal.SIGKILL) - + # Ensure the process has terminated process.wait() logging.info("Uninstalling neuron templates") uninstall_templates(templates_dir) - \ No newline at end of file diff --git a/tests/e2e_tests/subcommands/stake/test_childkeys.py b/tests/e2e_tests/subcommands/stake/test_childkeys.py index 3cfd42513..5502529ca 100644 --- a/tests/e2e_tests/subcommands/stake/test_childkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_childkeys.py @@ -62,10 +62,10 @@ async def wait(): # wait rate limit, until we are allowed to get children rate_limit = ( - subtensor.query_constant( - module_name="SubtensorModule", constant_name="InitialTempo" - ).value - * 2 + subtensor.query_constant( + module_name="SubtensorModule", constant_name="InitialTempo" + ).value + * 2 ) curr_block = subtensor.get_current_block() await wait_interval(rate_limit + curr_block + 1, subtensor) @@ -101,7 +101,6 @@ async def wait(): "True", "--wait_for_finalization", "True", - ], ) @@ -114,8 +113,8 @@ async def wait(): normalized_proportions = prepare_child_proportions(children_with_proportions) assert ( - children_info[0][0] == normalized_proportions[0][0] - and children_info[1][0] == normalized_proportions[1][0] + children_info[0][0] == normalized_proportions[0][0] + and children_info[1][0] == normalized_proportions[1][0] ), "Incorrect proportions set" # Test 2: Get children information @@ -161,7 +160,7 @@ async def wait(): await wait() assert ( - subtensor.get_children(netuid=1, hotkey=alice_keypair.ss58_address) == [] + subtensor.get_children(netuid=1, hotkey=alice_keypair.ss58_address) == [] ), "Failed to revoke children hotkeys" await wait() @@ -204,7 +203,7 @@ async def test_set_revoke_childkey_take(local_chain, capsys): - Set a childkey take amount for Alice - Verify the setting operation was successful - Retrieve the set childkey take amount - - Verify the retrieved amount is correct + - Verify the retrieved amount is correct This test ensures the proper functioning of setting and retrieving childkey take amounts in the staking system. @@ -242,7 +241,10 @@ async def test_set_revoke_childkey_take(local_chain, capsys): ) output = capsys.readouterr().out - assert "The childkey take for 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY is now \nset to 12.000%." in output + assert ( + "The childkey take for 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY is now \nset to 12.000%." + in output + ) # Test 1: Set multiple children alice_exec_command( @@ -258,9 +260,12 @@ async def test_set_revoke_childkey_take(local_chain, capsys): ) output = capsys.readouterr().out - assert "The childkey take for 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY is \n12.000%." in output - - + assert ( + "The childkey take for 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY is \n12.000%." + in output + ) + + @pytest.mark.asyncio async def test_set_revoke_children_singular(local_chain, capsys): """ @@ -310,10 +315,10 @@ async def wait(): # wait rate limit, until we are allowed to get children rate_limit = ( - subtensor.query_constant( - module_name="SubtensorModule", constant_name="InitialTempo" - ).value - * 2 + subtensor.query_constant( + module_name="SubtensorModule", constant_name="InitialTempo" + ).value + * 2 ) curr_block = subtensor.get_current_block() await wait_interval(rate_limit + curr_block + 1, subtensor) @@ -351,7 +356,7 @@ async def wait(): "True", ], ) - + output = capsys.readouterr().out assert "5FHn… │ 60.000%" in output @@ -359,9 +364,9 @@ async def wait(): subtensor = bittensor.subtensor(network="ws://localhost:9945") children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) - + assert len(children_info) == 1, "Failed to set child hotkeys" - + # Test 2: Set second child alice_exec_command( SetChildrenCommand, @@ -414,11 +419,11 @@ async def wait(): assert "5HGj… │ 40.000%" in output await wait() - + subtensor = bittensor.subtensor(network="ws://localhost:9945") children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) assert len(children_info) == 1, "Failed to revoke child hotkey" - + # Test 4: Revoke second child alice_exec_command( RevokeChildrenCommand, @@ -443,7 +448,7 @@ async def wait(): subtensor = bittensor.subtensor(network="ws://localhost:9945") children_info = subtensor.get_children(hotkey=alice_keypair.ss58_address, netuid=1) assert len(children_info) == 0, "Failed to revoke child hotkey" - + # Test 4: Get children after revocation alice_exec_command( GetChildrenCommand, @@ -461,5 +466,7 @@ async def wait(): ], ) output = capsys.readouterr().out - assert "There are currently no child hotkeys on subnet 1 with Parent HotKey \n5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY." in output - + assert ( + "There are currently no child hotkeys on subnet 1 with Parent HotKey \n5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY." + in output + ) From 06e16bb686ed460032508c29db8c2cffa2c93b6d Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 28 Aug 2024 12:25:15 -0700 Subject: [PATCH 21/27] --all flag, other small changes --- bittensor/commands/stake.py | 268 +++++++++--------- bittensor/commands/unstake.py | 22 +- .../subcommands/stake/test_childkeys.py | 6 +- 3 files changed, 146 insertions(+), 150 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index ec94291b1..6b2e34b7c 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -41,6 +41,36 @@ console = bittensor.__console__ +def get_netuid(cli, subtensor): + console = Console() + if not cli.config.is_set("netuid"): + try: + cli.config.netuid = int(Prompt.ask("Enter netuid")) + except ValueError: + console.print("[red]Invalid input. Please enter a valid integer for netuid.[/red]") + return False, -1 + netuid = cli.config.netuid + if not subtensor.subnet_exists(netuid=netuid): + console.print( + "[red]Network with netuid {} does not exist. Please try again.[/red]".format( + netuid + ) + ) + return False, -1 + return True, netuid + + +def get_hotkey(wallet: "bittensor.wallet", config: "bittensor.config") -> str: + if wallet and wallet.hotkey: + return wallet.hotkey.ss58_address + elif config.is_set("hotkey"): + return config.hotkey + elif config.is_set("ss58"): + return config.ss58 + else: + return Prompt.ask("Enter hotkey (ss58)") + + class StakeCommand: """ Executes the ``add`` command to stake tokens to one or more hotkeys from a user's coldkey on the Bittensor network. @@ -173,7 +203,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # If the max_stake is greater than the current wallet balance, stake the entire balance. stake_amount_tao: float = min(stake_amount_tao, wallet_balance.tao) if ( - stake_amount_tao <= 0.00001 + stake_amount_tao <= 0.00001 ): # Threshold because of fees, might create a loop otherwise # Skip hotkey if max_stake is less than current stake. continue @@ -196,13 +226,13 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Ask to stake if not config.no_prompt: if not Confirm.ask( - f"Do you want to stake to the following keys from {wallet.name}:\n" - + "".join( - [ - f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" - for hotkey, amount in zip(final_hotkeys, final_amounts) - ] - ) + f"Do you want to stake to the following keys from {wallet.name}:\n" + + "".join( + [ + f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" + for hotkey, amount in zip(final_hotkeys, final_amounts) + ] + ) ): return None @@ -231,24 +261,24 @@ def check_config(cls, config: "bittensor.config"): config.wallet.name = str(wallet_name) if ( - not config.is_set("wallet.hotkey") - and not config.no_prompt - and not config.wallet.get("all_hotkeys") - and not config.wallet.get("hotkeys") + not config.is_set("wallet.hotkey") + and not config.no_prompt + and not config.wallet.get("all_hotkeys") + and not config.wallet.get("hotkeys") ): hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) # Get amount. if ( - not config.get("amount") - and not config.get("stake_all") - and not config.get("max_stake") + not config.get("amount") + and not config.get("stake_all") + and not config.get("max_stake") ): if not Confirm.ask( - "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", defaults.wallet.name) - ) + "Stake all Tao from account: [bold]'{}'[/bold]?".format( + config.wallet.get("name", defaults.wallet.name) + ) ): amount = Prompt.ask("Enter Tao amount to stake") try: @@ -327,8 +357,8 @@ def _get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: path=wallet.path, name=wallet.name, hotkey=hotkey_file_name ) if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() + hotkey_for_name.hotkey_file.exists_on_device() + and not hotkey_for_name.hotkey_file.is_encrypted() ): hotkey_wallets.append(hotkey_for_name) except Exception: @@ -391,7 +421,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) def get_stake_accounts( - wallet, subtensor + wallet, subtensor ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Get stake account details for the given wallet. @@ -420,7 +450,7 @@ def get_stake_accounts( } def get_stakes_from_hotkeys( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from hotkeys for the provided wallet. @@ -437,8 +467,8 @@ def get_stakes_from_hotkeys( [ n.emission for n in subtensor.get_all_neurons_for_pubkey( - hot.hotkey.ss58_address - ) + hot.hotkey.ss58_address + ) ] ) hotkey_stake = subtensor.get_stake_for_coldkey_and_hotkey( @@ -453,7 +483,7 @@ def get_stakes_from_hotkeys( return stakes def get_stakes_from_delegates( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from delegates for the provided wallet. @@ -479,13 +509,13 @@ def get_stakes_from_delegates( "name": delegate_name, "stake": nom[1], "rate": dele.total_daily_return.tao - * (nom[1] / dele.total_stake.tao), + * (nom[1] / dele.total_stake.tao), } return stakes def get_all_wallet_accounts( - wallets, - subtensor, + wallets, + subtensor, ) -> List[Dict[str, Dict[str, Union[str, Balance]]]]: """Fetch stake accounts for all provided wallets using a ThreadPool. @@ -550,9 +580,9 @@ def get_all_wallet_accounts( @staticmethod def check_config(config: "bittensor.config"): if ( - not config.get("all", d=None) - and not config.is_set("wallet.name") - and not config.no_prompt + not config.get("all", d=None) + and not config.is_set("wallet.name") + and not config.no_prompt ): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) @@ -609,29 +639,17 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): wallet = bittensor.wallet(config=cli.config) # Get values if not set. - if not cli.config.is_set("netuid"): - cli.config.netuid = int(Prompt.ask("Enter netuid")) - - netuid = cli.config.netuid - total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and not (0 <= netuid <= total_subnets): - console.print("Netuid is outside the current subnet range") + exists, netuid = get_netuid(cli, subtensor) + if not exists: return # get parent hotkey - if wallet and wallet.hotkey: - hotkey = wallet.hotkey.ss58_address - elif cli.config.is_set("hotkey"): - hotkey = cli.config.hotkey - elif cli.config.is_set("ss58"): - hotkey = cli.config.ss58 - else: - hotkey = Prompt.ask("Enter hotkey (ss58)") - + hotkey = get_hotkey(wallet, cli.config) if not wallet_utils.is_valid_ss58_address(hotkey): console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return + if not cli.config.is_set("take"): cli.config.take = Prompt.ask( "Enter the percentage of take for your child hotkey (between 0 and 0.18 representing 0-18%)" @@ -722,17 +740,12 @@ def add_args(parser: argparse.ArgumentParser): default=True, help="""Prompt for confirmation before proceeding.""", ) - set_childkey_take_parser.add_argument( - "--no_prompt", - dest="prompt", - action="store_false", - help="""Disable prompt for confirmation before proceeding.""", - ) set_childkey_take_parser.add_argument( "--y", + "--no_prompt", dest="prompt", action="store_false", - help="""Defaults to Yes for all prompts.""", + help="""Disable prompt for confirmation before proceeding. Defaults to Yes for all prompts.""", ) bittensor.wallet.add_args(set_childkey_take_parser) bittensor.subtensor.add_args(set_childkey_take_parser) @@ -771,29 +784,17 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): wallet = bittensor.wallet(config=cli.config) # Get values if not set. - if not cli.config.is_set("netuid"): - cli.config.netuid = int(Prompt.ask("Enter netuid")) - - netuid = cli.config.netuid - total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and not (0 <= netuid <= total_subnets): - console.print("Netuid is outside the current subnet range") + exists, netuid = get_netuid(cli, subtensor) + if not exists: return # get parent hotkey - if wallet and wallet.hotkey: - hotkey = wallet.hotkey.ss58_address - elif cli.config.is_set("hotkey"): - hotkey = cli.config.hotkey - elif cli.config.is_set("ss58"): - hotkey = cli.config.ss58 - else: - hotkey = Prompt.ask("Enter hotkey (ss58)") - + hotkey = get_hotkey(wallet, cli.config) if not wallet_utils.is_valid_ss58_address(hotkey): console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return + take_u16 = subtensor.get_childkey_take( netuid=netuid, hotkey=hotkey, @@ -897,29 +898,17 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): wallet = bittensor.wallet(config=cli.config) # Get values if not set. - if not cli.config.is_set("netuid"): - cli.config.netuid = int(Prompt.ask("Enter netuid")) - - netuid = cli.config.netuid - total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and not (0 <= netuid < total_subnets): - console.print("Netuid is outside the current subnet range") + exists, netuid = get_netuid(cli, subtensor) + if not exists: return # get parent hotkey - if wallet and wallet.hotkey: - hotkey = wallet.hotkey.ss58_address - elif cli.config.is_set("hotkey"): - hotkey = cli.config.hotkey - elif cli.config.is_set("ss58"): - hotkey = cli.config.ss58 - else: - hotkey = Prompt.ask("Enter parent hotkey (ss58)") - + hotkey = get_hotkey(wallet, cli.config) if not wallet_utils.is_valid_ss58_address(hotkey): console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return + # get current children curr_children = GetChildrenCommand.retrieve_children( subtensor=subtensor, @@ -1070,17 +1059,12 @@ def add_args(parser: argparse.ArgumentParser): default=True, help="""Prompt for confirmation before proceeding.""", ) - set_children_parser.add_argument( - "--no_prompt", - dest="prompt", - action="store_false", - help="""Disable prompt for confirmation before proceeding.""", - ) set_children_parser.add_argument( "--y", + "--no_prompt", dest="prompt", action="store_false", - help="""Defaults to Yes for all prompts.""", + help="""Disable prompt for confirmation before proceeding. Defaults to Yes for all prompts.""", ) bittensor.wallet.add_args(set_children_parser) bittensor.subtensor.add_args(set_children_parser) @@ -1091,9 +1075,9 @@ def print_current_stake(subtensor, children, hotkey): parent_stake = subtensor.get_total_stake_for_hotkey(ss58_address=hotkey) console.print("Current Status:") console.print( - f"Parent Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True + f"My Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True ) - console.print(f"Total Parent Stake: {parent_stake}τ") + console.print(f"Total Stake: {parent_stake}τ") for child in children: child_stake = subtensor.get_total_stake_for_hotkey(child) console.print( @@ -1133,6 +1117,9 @@ def run(cli: "bittensor.cli"): config=cli.config, log_verbose=False ) return GetChildrenCommand._run(cli, subtensor) + except Exception as e: + console = Console() + console.print(f":cross_mark:[red] An error occurred: {str(e)}[/red]") finally: if "subtensor" in locals(): subtensor.close() @@ -1143,41 +1130,45 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console = Console() wallet = bittensor.wallet(config=cli.config) - # set netuid - if not cli.config.is_set("netuid"): - cli.config.netuid = int(Prompt.ask("Enter netuid")) - netuid = cli.config.netuid - total_subnets = subtensor.get_total_subnets() - if total_subnets is not None and not (0 <= netuid < total_subnets): - console.print("Netuid is outside the current subnet range") - return + # check all + if not cli.config.is_set("all"): + exists, netuid = get_netuid(cli, subtensor) + if not exists: + return # get parent hotkey - if wallet and wallet.hotkey: - hotkey = wallet.hotkey.ss58_address - elif cli.config.is_set("hotkey"): - hotkey = cli.config.hotkey - elif cli.config.is_set("ss58"): - hotkey = cli.config.ss58 - else: - hotkey = Prompt.ask("Enter parent hotkey (ss58)") - + hotkey = get_hotkey(wallet, cli.config) if not wallet_utils.is_valid_ss58_address(hotkey): console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return - children = subtensor.get_children(hotkey, netuid) - hotkey_stake = subtensor.get_total_stake_for_hotkey(hotkey) - - GetChildrenCommand.render_table( - subtensor, hotkey, hotkey_stake, children, netuid, True - ) + try: + netuids = subtensor.get_all_subnet_netuids() if cli.config.is_set("all") else [netuid] + hotkey_stake = GetChildrenCommand.get_parent_stake_info(console, subtensor, hotkey) + for netuid in netuids: + children = subtensor.get_children(hotkey, netuid) + if children: + GetChildrenCommand.render_table( + subtensor, hotkey, hotkey_stake, children, netuid, not cli.config.is_set("all") + ) + except Exception as e: + console.print(f":cross_mark:[red] An error occurred while retrieving children: {str(e)}[/red]") + return return children + @staticmethod + def get_parent_stake_info(console, subtensor, hotkey): + hotkey_stake = subtensor.get_total_stake_for_hotkey(hotkey) + console.print( + f"\nYour Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True + ) + console.print(f"Total Stake: {hotkey_stake}τ") + return hotkey_stake + @staticmethod def retrieve_children( - subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool + subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool ) -> list[tuple[int, str]]: """ @@ -1193,13 +1184,18 @@ def retrieve_children( List[str]: A list of children hotkeys. """ - children = subtensor.get_children(hotkey, netuid) - if render_table: - hotkey_stake = subtensor.get_total_stake_for_hotkey(hotkey) - GetChildrenCommand.render_table( - subtensor, hotkey, hotkey_stake, children, netuid, False - ) - return children + try: + children = subtensor.get_children(hotkey, netuid) + if render_table: + hotkey_stake = subtensor.get_total_stake_for_hotkey(hotkey) + GetChildrenCommand.render_table( + subtensor, hotkey, hotkey_stake, children, netuid, False + ) + return children + except Exception as e: + console = Console() + console.print(f":cross_mark:[red] An error occurred while retrieving children: {str(e)}[/red]") + return [] @staticmethod def check_config(config: "bittensor.config"): @@ -1222,18 +1218,19 @@ def add_args(parser: argparse.ArgumentParser): ) parser.add_argument("--netuid", dest="netuid", type=int, required=False) parser.add_argument("--hotkey", dest="hotkey", type=str, required=False) + parser.add_argument('--all', dest='all', action='store_true', help='Retrieve children from all subnets.') bittensor.wallet.add_args(parser) bittensor.subtensor.add_args(parser) @staticmethod def render_table( - subtensor: "bittensor.subtensor", - hotkey: str, - hotkey_stake: "Balance", - children: list[Tuple[int, str]], - netuid: int, - prompt: bool, + subtensor: "bittensor.subtensor", + hotkey: str, + hotkey_stake: "Balance", + children: list[Tuple[int, str]], + netuid: int, + prompt: bool, ): """ @@ -1284,7 +1281,7 @@ def render_table( if not children: console.print(table) console.print( - f"[bold red]There are currently no child hotkeys on subnet {netuid} with Parent HotKey {hotkey}.[/bold red]" + f"[bold white]There are currently no child hotkeys on subnet {netuid} with Parent HotKey {hotkey}.[/bold white]" ) if prompt: command = f"btcli stake set_children --children --hotkey --netuid {netuid} --proportion " @@ -1293,10 +1290,7 @@ def render_table( ) return - console.print( - f"Parent Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True - ) - console.print(f"Total Parent Stake: {hotkey_stake}τ") + console.print(f"\nChildren for netuid: {netuid} ", style="cyan") # calculate totals total_proportion = 0 diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index 89fc6e77a..699c3114e 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -351,9 +351,10 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # get parent hotkey if wallet and wallet.hotkey: hotkey = wallet.hotkey.ss58_address - console.print(f"Hotkey is {hotkey}") elif cli.config.is_set("hotkey"): hotkey = cli.config.hotkey + elif cli.config.is_set("ss58"): + hotkey = cli.config.ss58 else: hotkey = Prompt.ask("Enter parent hotkey (ss58)") @@ -394,8 +395,14 @@ def check_config(config: "bittensor.config"): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) + hotkey_or_ss58 = Prompt.ask( + "Enter hotkey name or ss58", default=defaults.wallet.hotkey + ) + if wallet_utils.is_valid_ss58_address(hotkey_or_ss58): + config.ss58 = str(hotkey_or_ss58) + else: + config.wallet.hotkey = str(hotkey_or_ss58) + @staticmethod def add_args(parser: argparse.ArgumentParser): @@ -425,17 +432,12 @@ def add_args(parser: argparse.ArgumentParser): default=True, help="""Prompt for confirmation before proceeding.""", ) - parser.add_argument( - "--no_prompt", - dest="prompt", - action="store_false", - help="""Disable prompt for confirmation before proceeding.""", - ) parser.add_argument( "--y", + "--no_prompt", dest="prompt", action="store_false", - help="""Defaults to Yes for all prompts.""", + help="""Disable prompt for confirmation before proceeding. Defaults to Yes for all prompts.""", ) bittensor.wallet.add_args(parser) bittensor.subtensor.add_args(parser) diff --git a/tests/e2e_tests/subcommands/stake/test_childkeys.py b/tests/e2e_tests/subcommands/stake/test_childkeys.py index 5502529ca..1715c6f7d 100644 --- a/tests/e2e_tests/subcommands/stake/test_childkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_childkeys.py @@ -50,11 +50,11 @@ async def test_set_revoke_children_multiple(local_chain, capsys): bob_keypair, bob_exec_command, bob_wallet = setup_wallet("//Bob") eve_keypair, eve_exec_command, eve_wallet = setup_wallet("//Eve") - alice_exec_command(RegisterSubnetworkCommand, ["s", "create"]) - assert local_chain.query("SubtensorModule", "NetworksAdded", [1]).serialize() + # alice_exec_command(RegisterSubnetworkCommand, ["s", "create"]) + # assert local_chain.query("SubtensorModule", "NetworksAdded", [1]).serialize() for exec_command in [alice_exec_command, bob_exec_command, eve_exec_command]: - exec_command(RegisterCommand, ["s", "register", "--netuid", "1"]) + exec_command(RegisterCommand, ["s", "register", "--netuid", "3"]) alice_exec_command(StakeCommand, ["stake", "add", "--amount", "100000"]) From f643c7bfc7215613056dd82e5f0f5bf3cf085f17 Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 28 Aug 2024 13:05:19 -0700 Subject: [PATCH 22/27] ruff --- bittensor/commands/stake.py | 119 +++++++++++++++++++--------------- bittensor/commands/unstake.py | 1 - 2 files changed, 68 insertions(+), 52 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 6b2e34b7c..6c74a6cb3 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -47,7 +47,9 @@ def get_netuid(cli, subtensor): try: cli.config.netuid = int(Prompt.ask("Enter netuid")) except ValueError: - console.print("[red]Invalid input. Please enter a valid integer for netuid.[/red]") + console.print( + "[red]Invalid input. Please enter a valid integer for netuid.[/red]" + ) return False, -1 netuid = cli.config.netuid if not subtensor.subnet_exists(netuid=netuid): @@ -203,7 +205,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # If the max_stake is greater than the current wallet balance, stake the entire balance. stake_amount_tao: float = min(stake_amount_tao, wallet_balance.tao) if ( - stake_amount_tao <= 0.00001 + stake_amount_tao <= 0.00001 ): # Threshold because of fees, might create a loop otherwise # Skip hotkey if max_stake is less than current stake. continue @@ -226,13 +228,13 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Ask to stake if not config.no_prompt: if not Confirm.ask( - f"Do you want to stake to the following keys from {wallet.name}:\n" - + "".join( - [ - f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" - for hotkey, amount in zip(final_hotkeys, final_amounts) - ] - ) + f"Do you want to stake to the following keys from {wallet.name}:\n" + + "".join( + [ + f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" + for hotkey, amount in zip(final_hotkeys, final_amounts) + ] + ) ): return None @@ -261,24 +263,24 @@ def check_config(cls, config: "bittensor.config"): config.wallet.name = str(wallet_name) if ( - not config.is_set("wallet.hotkey") - and not config.no_prompt - and not config.wallet.get("all_hotkeys") - and not config.wallet.get("hotkeys") + not config.is_set("wallet.hotkey") + and not config.no_prompt + and not config.wallet.get("all_hotkeys") + and not config.wallet.get("hotkeys") ): hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) # Get amount. if ( - not config.get("amount") - and not config.get("stake_all") - and not config.get("max_stake") + not config.get("amount") + and not config.get("stake_all") + and not config.get("max_stake") ): if not Confirm.ask( - "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", defaults.wallet.name) - ) + "Stake all Tao from account: [bold]'{}'[/bold]?".format( + config.wallet.get("name", defaults.wallet.name) + ) ): amount = Prompt.ask("Enter Tao amount to stake") try: @@ -357,8 +359,8 @@ def _get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: path=wallet.path, name=wallet.name, hotkey=hotkey_file_name ) if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() + hotkey_for_name.hotkey_file.exists_on_device() + and not hotkey_for_name.hotkey_file.is_encrypted() ): hotkey_wallets.append(hotkey_for_name) except Exception: @@ -421,7 +423,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) def get_stake_accounts( - wallet, subtensor + wallet, subtensor ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Get stake account details for the given wallet. @@ -450,7 +452,7 @@ def get_stake_accounts( } def get_stakes_from_hotkeys( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from hotkeys for the provided wallet. @@ -467,8 +469,8 @@ def get_stakes_from_hotkeys( [ n.emission for n in subtensor.get_all_neurons_for_pubkey( - hot.hotkey.ss58_address - ) + hot.hotkey.ss58_address + ) ] ) hotkey_stake = subtensor.get_stake_for_coldkey_and_hotkey( @@ -483,7 +485,7 @@ def get_stakes_from_hotkeys( return stakes def get_stakes_from_delegates( - subtensor, wallet + subtensor, wallet ) -> Dict[str, Dict[str, Union[str, Balance]]]: """Fetch stakes from delegates for the provided wallet. @@ -509,13 +511,13 @@ def get_stakes_from_delegates( "name": delegate_name, "stake": nom[1], "rate": dele.total_daily_return.tao - * (nom[1] / dele.total_stake.tao), + * (nom[1] / dele.total_stake.tao), } return stakes def get_all_wallet_accounts( - wallets, - subtensor, + wallets, + subtensor, ) -> List[Dict[str, Dict[str, Union[str, Balance]]]]: """Fetch stake accounts for all provided wallets using a ThreadPool. @@ -580,9 +582,9 @@ def get_all_wallet_accounts( @staticmethod def check_config(config: "bittensor.config"): if ( - not config.get("all", d=None) - and not config.is_set("wallet.name") - and not config.no_prompt + not config.get("all", d=None) + and not config.is_set("wallet.name") + and not config.no_prompt ): wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) config.wallet.name = str(wallet_name) @@ -649,7 +651,6 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return - if not cli.config.is_set("take"): cli.config.take = Prompt.ask( "Enter the percentage of take for your child hotkey (between 0 and 0.18 representing 0-18%)" @@ -794,7 +795,6 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return - take_u16 = subtensor.get_childkey_take( netuid=netuid, hotkey=hotkey, @@ -908,7 +908,6 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): console.print(f":cross_mark:[red] Invalid SS58 address: {hotkey}[/red]") return - # get current children curr_children = GetChildrenCommand.retrieve_children( subtensor=subtensor, @@ -1074,9 +1073,7 @@ def print_current_stake(subtensor, children, hotkey): console = Console() parent_stake = subtensor.get_total_stake_for_hotkey(ss58_address=hotkey) console.print("Current Status:") - console.print( - f"My Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True - ) + console.print(f"My Hotkey: {hotkey} | ", style="cyan", end="", no_wrap=True) console.print(f"Total Stake: {parent_stake}τ") for child in children: child_stake = subtensor.get_total_stake_for_hotkey(child) @@ -1143,16 +1140,29 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): return try: - netuids = subtensor.get_all_subnet_netuids() if cli.config.is_set("all") else [netuid] - hotkey_stake = GetChildrenCommand.get_parent_stake_info(console, subtensor, hotkey) + netuids = ( + subtensor.get_all_subnet_netuids() + if cli.config.is_set("all") + else [netuid] + ) + hotkey_stake = GetChildrenCommand.get_parent_stake_info( + console, subtensor, hotkey + ) for netuid in netuids: children = subtensor.get_children(hotkey, netuid) if children: GetChildrenCommand.render_table( - subtensor, hotkey, hotkey_stake, children, netuid, not cli.config.is_set("all") + subtensor, + hotkey, + hotkey_stake, + children, + netuid, + not cli.config.is_set("all"), ) except Exception as e: - console.print(f":cross_mark:[red] An error occurred while retrieving children: {str(e)}[/red]") + console.print( + f":cross_mark:[red] An error occurred while retrieving children: {str(e)}[/red]" + ) return return children @@ -1168,7 +1178,7 @@ def get_parent_stake_info(console, subtensor, hotkey): @staticmethod def retrieve_children( - subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool + subtensor: "bittensor.subtensor", hotkey: str, netuid: int, render_table: bool ) -> list[tuple[int, str]]: """ @@ -1194,7 +1204,9 @@ def retrieve_children( return children except Exception as e: console = Console() - console.print(f":cross_mark:[red] An error occurred while retrieving children: {str(e)}[/red]") + console.print( + f":cross_mark:[red] An error occurred while retrieving children: {str(e)}[/red]" + ) return [] @staticmethod @@ -1218,19 +1230,24 @@ def add_args(parser: argparse.ArgumentParser): ) parser.add_argument("--netuid", dest="netuid", type=int, required=False) parser.add_argument("--hotkey", dest="hotkey", type=str, required=False) - parser.add_argument('--all', dest='all', action='store_true', help='Retrieve children from all subnets.') + parser.add_argument( + "--all", + dest="all", + action="store_true", + help="Retrieve children from all subnets.", + ) bittensor.wallet.add_args(parser) bittensor.subtensor.add_args(parser) @staticmethod def render_table( - subtensor: "bittensor.subtensor", - hotkey: str, - hotkey_stake: "Balance", - children: list[Tuple[int, str]], - netuid: int, - prompt: bool, + subtensor: "bittensor.subtensor", + hotkey: str, + hotkey_stake: "Balance", + children: list[Tuple[int, str]], + netuid: int, + prompt: bool, ): """ diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index 699c3114e..e52e28094 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -403,7 +403,6 @@ def check_config(config: "bittensor.config"): else: config.wallet.hotkey = str(hotkey_or_ss58) - @staticmethod def add_args(parser: argparse.ArgumentParser): parser = parser.add_parser( From 79039672a2f8e79376955de5e18331d737bba376 Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 28 Aug 2024 13:10:13 -0700 Subject: [PATCH 23/27] add --yes --- bittensor/commands/stake.py | 2 ++ bittensor/commands/unstake.py | 1 + 2 files changed, 3 insertions(+) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 6c74a6cb3..79cdd3d35 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -743,6 +743,7 @@ def add_args(parser: argparse.ArgumentParser): ) set_childkey_take_parser.add_argument( "--y", + "--yes", "--no_prompt", dest="prompt", action="store_false", @@ -1060,6 +1061,7 @@ def add_args(parser: argparse.ArgumentParser): ) set_children_parser.add_argument( "--y", + "--yes", "--no_prompt", dest="prompt", action="store_false", diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py index e52e28094..291aeb6e9 100644 --- a/bittensor/commands/unstake.py +++ b/bittensor/commands/unstake.py @@ -433,6 +433,7 @@ def add_args(parser: argparse.ArgumentParser): ) parser.add_argument( "--y", + "--yes", "--no_prompt", dest="prompt", action="store_false", From a41ea542fe5a9689be23c19d5c70a62434dac01a Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 28 Aug 2024 13:23:54 -0700 Subject: [PATCH 24/27] mypy --- bittensor/subtensor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 9326c1425..2cc01b4d7 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -4686,7 +4686,7 @@ def make_substrate_call_with_retry(encoded_coldkey_: List[int]): ############################ def get_childkey_take( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey: str, netuid: int, block: Optional[int] = None ) -> Optional[int]: """ Get the childkey take of a hotkey on a specific network. @@ -4713,6 +4713,7 @@ def get_childkey_take( except Exception as e: print(f"Unexpected error in get_children: {e}") return None + return None def get_children(self, hotkey, netuid) -> list[tuple[int, str]] | list[Any] | None: """ From c12a617e7ba9fb720b92389488c42799f84506e6 Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 28 Aug 2024 13:31:06 -0700 Subject: [PATCH 25/27] ruff --- bittensor/subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 2cc01b4d7..ac22a3a14 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -4686,7 +4686,7 @@ def make_substrate_call_with_retry(encoded_coldkey_: List[int]): ############################ def get_childkey_take( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey: str, netuid: int, block: Optional[int] = None ) -> Optional[int]: """ Get the childkey take of a hotkey on a specific network. From 4f70ce4776205e2f328f1160264baf4ab641f0d8 Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 28 Aug 2024 13:43:49 -0700 Subject: [PATCH 26/27] fix network test --- tests/unit_tests/test_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 731285c22..c651eaa57 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -269,7 +269,7 @@ def mock_add_argument(*args, **kwargs): ("localhost", "local", "localhost"), # Edge cases (None, None, None), - ("unknown", "unknown", "unknown"), + ("unknown", "unknown network", "unknown"), ], ) def test_determine_chain_endpoint_and_network( From 7ee01fd7583d87e0724b7f3048db5353b8c8c33c Mon Sep 17 00:00:00 2001 From: opendansor Date: Wed, 28 Aug 2024 13:51:52 -0700 Subject: [PATCH 27/27] final touches --- bittensor/commands/stake.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py index 79cdd3d35..132529a13 100644 --- a/bittensor/commands/stake.py +++ b/bittensor/commands/stake.py @@ -40,8 +40,13 @@ console = bittensor.__console__ +MAX_CHILDREN = 5 -def get_netuid(cli, subtensor): + +def get_netuid( + cli: "bittensor.cli", subtensor: "bittensor.subtensor" +) -> Tuple[bool, int]: + """Retrieve and validate the netuid from the user or configuration.""" console = Console() if not cli.config.is_set("netuid"): try: @@ -63,6 +68,7 @@ def get_netuid(cli, subtensor): def get_hotkey(wallet: "bittensor.wallet", config: "bittensor.config") -> str: + """Retrieve the hotkey from the wallet or config.""" if wallet and wallet.hotkey: return wallet.hotkey.ss58_address elif config.is_set("hotkey"): @@ -937,7 +943,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): proposed_children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] # Set max 5 children - if len(proposed_children) > 5: + if len(proposed_children) > MAX_CHILDREN: console.print( ":cross_mark:[red] Too many children. Maximum 5 children per hotkey[/red]" )