diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 1d9c601fd3..969423db01 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -10,6 +10,7 @@ on: pull_request: branches: [main, development, staging] + types: [ opened, synchronize, reopened, ready_for_review ] workflow_dispatch: inputs: @@ -27,7 +28,7 @@ jobs: # Job to find all test files find-tests: runs-on: ubuntu-latest - if: github.event.pull_request.draft == false && github.event_name != 'pull_request' + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.draft == false }} outputs: test-files: ${{ steps.get-tests.outputs.test-files }} steps: diff --git a/bittensor/cli.py b/bittensor/cli.py index c441e4ae7e..6bec2c21e3 100644 --- a/bittensor/cli.py +++ b/bittensor/cli.py @@ -72,6 +72,7 @@ SetChildCommand, SetChildrenCommand, GetChildrenCommand, + ChildHotkeysCommand, RevokeChildCommand, RevokeChildrenCommand, ) @@ -177,6 +178,7 @@ "revoke_child": RevokeChildCommand, "set_children": SetChildrenCommand, "revoke_children": RevokeChildrenCommand, + "child_hotkeys": ChildHotkeysCommand, }, }, "weights": { diff --git a/bittensor/commands/__init__.py b/bittensor/commands/__init__.py index f2d78cd188..8910c9709d 100644 --- a/bittensor/commands/__init__.py +++ b/bittensor/commands/__init__.py @@ -70,6 +70,7 @@ from .stake.show import StakeShow from .stake.revoke_child import RevokeChildCommand from .stake.revoke_children import RevokeChildrenCommand +from .stake.child_hotkeys import ChildHotkeysCommand from .unstake import UnStakeCommand from .overview import OverviewCommand from .register import ( diff --git a/bittensor/commands/stake/child_hotkeys.py b/bittensor/commands/stake/child_hotkeys.py new file mode 100644 index 0000000000..6c999cd458 --- /dev/null +++ b/bittensor/commands/stake/child_hotkeys.py @@ -0,0 +1,401 @@ +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao + +# 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 +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of +# the Software. + +# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +import argparse + +from rich.table import Table +from rich.prompt import Prompt +from rich.console import Console +from rich.text import Text + +import bittensor +from .. import defaults, SetChildCommand, RevokeChildCommand # type: ignore +from ... import ChildInfo +from ...utils.formatting import u64_to_float + +console = bittensor.__console__ + + +class ChildHotkeysCommand: + """ + Executes the ``child_hotkeys`` command to get all child hotkeys on a specified subnet on the Bittensor network. + + This command is used to view delegated authority to different hotkeys on the subnet. + + Usage: + Users can specify the subnet and see the children and the proportion that is given to them. + + The command compiles a table showing: + + - ChildHotkey: The hotkey associated with the child. + - ParentHotKey: The hotkey associated with the parent. + - Proportion: The proportion that is assigned to them. + - Expiration: The expiration of the hotkey. + + Example usage:: + + btcli stake get_children --netuid 1 + + Note: + This command is for users who wish to see child hotkeys among different neurons (hotkeys) on the network. + """ + + @staticmethod + def run(cli: "bittensor.cli"): + r"""Set child hotkey.""" + try: + subtensor: "bittensor.subtensor" = bittensor.subtensor( + config=cli.config, log_verbose=False + ) + return ChildHotkeysCommand._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"): + 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")) + + # Parse from strings + netuid = cli.config.netuid + + children = subtensor.get_children_info( + netuid=netuid, + ) + + ChildHotkeysCommand.render_table_interactive( + cli, subtensor, children, netuid, wallet + ) + + return children + + @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): + parser = parser.add_parser( + "child_hotkeys", help="""Get child hotkeys on subnet.""" + ) + parser.add_argument("--netuid", dest="netuid", type=int, required=False) + bittensor.wallet.add_args(parser) + bittensor.subtensor.add_args(parser) + + @staticmethod + def render_help_details(): + console = Console() + console.print("\nColumn Information:") + console.print( + "[cyan]ChildHotkey:[/cyan] Truncated child hotkey associated with the child" + ) + console.print( + "[cyan]ParentHotKeys:[/cyan] The number of parent hotkeys associated with the child" + ) + console.print( + "[cyan]Proportion:[/cyan] Proportion of stake allocated to this child" + ) + console.print("[cyan]Total Stake:[/cyan] Total stake of the child") + console.print("[cyan]Emissions/Day:[/cyan] Emissions per day for this child") + console.print( + "[cyan]Return per 1000 TAO:[/cyan] Return per 1000 TAO staked for this child" + ) + console.print("[cyan]Take:[/cyan] Commission taken by the child") + + @staticmethod + def render_child_details( + cli, + subtensor, + child_info: ChildInfo, + netuid: int, + wallet: "bittensor.wallet", + ): + console = Console() + + ChildHotkeysCommand.print_child_table_singular(child_info) + + table = Table( + show_header=True, + header_style="bold magenta", + border_style="green", + style="green", + ) + + table.add_column("ParentHotKey", style="cyan", no_wrap=True) + table.add_column("Proportion", style="cyan", no_wrap=True, justify="right") + + for parent in child_info.parents: + table.add_row(str(parent[1]), str(parent[0])) + + console.print(table) + command = Prompt.ask( + "To revoke your portion from the child hotkey type 'revoke' and press enter (b for back, q for quit)" + ) + if command == "revoke": + ChildHotkeysCommand.revoke_child( + cli, subtensor, netuid, wallet, child_info.child_ss58 + ) + elif command == "b": + ChildHotkeysCommand._run(cli=cli, subtensor=subtensor) + elif command == "q": + console.clear() + return + + @staticmethod + def print_child_table_singular(child_info): + console = Console() + + # Initialize Rich table for pretty printing + table = Table( + show_header=True, + header_style="bold magenta", + border_style="green", + style="green", + ) + table.add_column("ChildHotkey", style="cyan", no_wrap=True) + table.add_column("Number of ParentHotKeys", style="cyan", no_wrap=True) + table.add_column( + "Total Proportion", style="cyan", no_wrap=True, justify="right" + ) + table.add_column("Total Stake", style="cyan", no_wrap=True, justify="right") + table.add_column("Emissions/Day", style="cyan", no_wrap=True, justify="right") + table.add_column( + "Return per 1000 TAO", style="cyan", no_wrap=True, justify="right" + ) + table.add_column("Take", style="cyan", no_wrap=True, justify="right") + table.add_row( + child_info.child_ss58, + str(len(child_info.parents)), + str(u64_to_float(child_info.proportion)), + str(child_info.total_stake), + str(child_info.emissions_per_day), + str(child_info.return_per_1000), + str(child_info.take), + ) + + console.print(table) + + @staticmethod + def render_table_interactive( + cli: "bittensor.cli", + subtensor: "bittensor.subtensor", + children: dict[str, list[ChildInfo]], + netuid: int, + wallet: "bittensor.wallet", + ): + console = Console() + + # Initialize Rich table for pretty printing + table = Table( + show_header=True, + header_style="bold magenta", + border_style="green", + style="green", + ) + + # 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("Number of ParentHotKeys", style="cyan", no_wrap=True) + table.add_column("Proportion", style="cyan", no_wrap=True, justify="right") + table.add_column("Total Stake", style="cyan", no_wrap=True, justify="right") + table.add_column("Emissions/Day", style="cyan", no_wrap=True, justify="right") + table.add_column( + "Return per 1000 TAO", style="cyan", no_wrap=True, justify="right" + ) + table.add_column("Take", style="cyan", no_wrap=True, justify="right") + + sum_proportion = 0.0 + sum_total_stake = 0.0 + sum_emissions_per_day = 0.0 + sum_return_per_1000 = 0.0 + sum_take = 0.0 + + child_hotkeys_set = set() + parent_hotkeys_set = set() + + if not children: + console.print(table) + # Summary Row + summary = Text( + "Total (0) | Total (0) | 0.000000 | 0.0000 | 0.0000 | 0.0000 | 0.000000", + style="dim", + ) + console.print(summary) + + console.print(f"There are currently no child hotkeys on subnet {netuid}.") + command = Prompt.ask( + "To add a child hotkey type 'add' and press enter (h for help, q for quit)" + ) + if command == "add": + ChildHotkeysCommand.add_child(cli, subtensor, netuid, wallet) + elif command == "h": + ChildHotkeysCommand.render_help_details() + ChildHotkeysCommand.render_table_interactive( + cli, subtensor, children, netuid, wallet + ) + else: + return + + # Flatten children dictionary into a list of tuples (child_ss58, ChildInfo) + flattened_children = [ + (child_ss58, child_info) + for child_ss58, child_infos in children.items() + for child_info in child_infos + ] + + # Sort flattened children by proportion (highest first) + sorted_children = sorted( + flattened_children, key=lambda item: item[1].proportion, reverse=True + ) + + # Populate table with children data + for index, (child_ss58, child_info) in enumerate(sorted_children, start=1): + table.add_row( + str(index), + child_ss58[:5] + "..." + child_ss58[-5:], + str(len(child_info.parents)), + str(u64_to_float(child_info.proportion)), + str(child_info.total_stake), + str(child_info.emissions_per_day), + str(child_info.return_per_1000), + str(child_info.take), + ) + # Update totals and sets + child_hotkeys_set.add(child_info.child_ss58) + parent_hotkeys_set.update(p[1] for p in child_info.parents) + sum_proportion += child_info.proportion + sum_total_stake += float(child_info.total_stake) + sum_emissions_per_day += float(child_info.emissions_per_day) + sum_return_per_1000 += float(child_info.return_per_1000) + sum_take += float(child_info.take) + + # Calculate averages + total_child_hotkeys = len(child_hotkeys_set) + total_parent_hotkeys = len(parent_hotkeys_set) + avg_emissions_per_day = ( + sum_emissions_per_day / total_child_hotkeys if total_child_hotkeys else 0 + ) + avg_return_per_1000 = ( + sum_return_per_1000 / total_child_hotkeys if total_child_hotkeys else 0 + ) + + # Print table to console + console.print(table) + + # Add a summary row with fixed-width fields + summary = Text( + f"Total ({total_child_hotkeys:3}) | Total ({total_parent_hotkeys:3}) | " + f"Total ({u64_to_float(sum_proportion):10.6f}) | Total ({sum_total_stake:10.4f}) | " + f"Avg ({avg_emissions_per_day:10.4f}) | Avg ({avg_return_per_1000:10.4f}) | " + f"Total ({sum_take:10.6f})", + style="dim", + ) + console.print(summary) + + command = Prompt.ask( + "To see more information enter child index (add for adding child hotkey, h for help, q for quit)" + ) + if command == "add": + ChildHotkeysCommand.add_child(cli, subtensor, netuid, wallet) + elif command == "h": + ChildHotkeysCommand.render_help_details() + ChildHotkeysCommand.render_table_interactive( + cli, subtensor, children, netuid, wallet + ) + elif command == "q": + return + else: + try: + selected_index = int(command) - 1 + if 0 <= selected_index < len(flattened_children): + selected_child_ss58, selected_child_info = flattened_children[ + selected_index + ] + console.print( + selected_child_info + ) # Print or process selected child info + ChildHotkeysCommand.render_child_details( + cli, subtensor, selected_child_info, netuid, wallet + ) + else: + console.print( + f"Invalid index: {command}. Please select a valid index." + ) + ChildHotkeysCommand.render_table_interactive( + cli, subtensor, children, netuid, wallet + ) + except ValueError: + console.print( + "Invalid input. Please enter a valid index or 'h' for help." + ) + ChildHotkeysCommand.render_table_interactive( + cli, subtensor, children, netuid, wallet + ) + + @staticmethod + def add_child(cli, subtensor, netuid, wallet): + child_hotkey = Prompt.ask("Enter child ss58 hotkey address") + proportion = Prompt.ask("Enter proportion to grant this child") + + try: + proportion = float(proportion) + + success, message = SetChildCommand.do_set_child_singular( + subtensor=subtensor, + wallet=wallet, + hotkey=wallet.hotkey.ss58_address, + child=child_hotkey, + netuid=netuid, + proportion=proportion, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + except ValueError: + console.print(f"proportion {proportion} needs to be a float <1.") + + ChildHotkeysCommand._run(cli=cli, subtensor=subtensor) + + @staticmethod + def revoke_child(cli, subtensor, netuid, wallet, child_hotkey): + try: + success, message = RevokeChildCommand.do_revoke_child_singular( + subtensor=subtensor, + wallet=wallet, + hotkey=wallet.hotkey.ss58_address, + child=child_hotkey, + netuid=netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + prompt=True, + ) + + except Exception: + pass + + ChildHotkeysCommand._run(cli=cli, subtensor=subtensor) diff --git a/bittensor/commands/stake/get_children_info.py b/bittensor/commands/stake/get_children_info.py index 206b218b61..770310991c 100644 --- a/bittensor/commands/stake/get_children_info.py +++ b/bittensor/commands/stake/get_children_info.py @@ -25,7 +25,7 @@ import bittensor from .. import defaults # type: ignore from ... import ChildInfo -from ...utils.formatting import u16_to_float +from ...utils.formatting import u64_to_float console = bittensor.__console__ @@ -167,7 +167,7 @@ def render_table(children: list[ChildInfo], netuid: int): str(index), child_info.child_ss58[:5] + "..." + child_info.child_ss58[-5:], str(len(child_info.parents)), - str(u16_to_float(child_info.proportion)), + str(u64_to_float(child_info.proportion)), str(child_info.total_stake), str(child_info.emissions_per_day), str(child_info.return_per_1000), @@ -199,9 +199,29 @@ def render_table(children: list[ChildInfo], netuid: int): # Add a summary row with fixed-width fields summary = Text( f"Total ({total_child_hotkeys:3}) | Total ({total_parent_hotkeys:3}) | " - f"Total ({u16_to_float(sum_proportion):10.6f}) | Total ({sum_total_stake:10.4f}) | " + f"Total ({u64_to_float(sum_proportion):10.6f}) | Total ({sum_total_stake:10.4f}) | " f"Avg ({avg_emissions_per_day:10.4f}) | Avg ({avg_return_per_1000:10.4f}) | " f"Total ({sum_take:10.6f})", style="dim", ) console.print(summary) + + @staticmethod + def render_help_details(): + console = Console() + console.print("\nColumn Information:") + console.print( + "[cyan]ChildHotkey:[/cyan] Truncated child hotkey associated with the child" + ) + console.print( + "[cyan]ParentHotKeys:[/cyan] The number of parent hotkeys associated with the child" + ) + console.print( + "[cyan]Proportion:[/cyan] Proportion of stake allocated to this child" + ) + console.print("[cyan]Total Stake:[/cyan] Total stake of the child") + console.print("[cyan]Emissions/Day:[/cyan] Emissions per day for this child") + console.print( + "[cyan]Return per 1000 TAO:[/cyan] Return per 1000 TAO staked for this child" + ) + console.print("[cyan]Take:[/cyan] Commission taken by the child") diff --git a/bittensor/commands/stake/revoke_children.py b/bittensor/commands/stake/revoke_children.py index 74bbe35f6d..030f6205f2 100644 --- a/bittensor/commands/stake/revoke_children.py +++ b/bittensor/commands/stake/revoke_children.py @@ -18,13 +18,12 @@ import argparse import re -from typing import Union, Tuple -import numpy as np -from numpy.typing import NDArray +from typing import Tuple, List from rich.prompt import Confirm, Prompt import bittensor from .. import defaults, GetChildrenCommand +from ...utils import is_valid_ss58_address console = bittensor.__console__ @@ -84,9 +83,13 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Parse from strings netuid = cli.config.netuid - children = np.array( - [str(x) for x in re.split(r"[ ,]+", cli.config.children)], dtype=str - ) + children = re.split(r"[ ,]+", cli.config.children.strip()) + + # Validate children SS58 addresses + for child in children: + if not is_valid_ss58_address(child): + console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") + return success, message = RevokeChildrenCommand.do_revoke_children_multiple( subtensor=subtensor, @@ -152,7 +155,7 @@ def do_revoke_children_multiple( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey: str, - children: Union[NDArray[str], list], + children: List[str], netuid: int, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -168,7 +171,7 @@ def do_revoke_children_multiple( Bittensor wallet object. hotkey (str): Parent hotkey. - children (np.ndarray): + children (List[str]): Children hotkeys. netuid (int): Unique identifier of for the subnet. diff --git a/bittensor/commands/stake/set_child.py b/bittensor/commands/stake/set_child.py index a417ca61d2..af83c1c420 100644 --- a/bittensor/commands/stake/set_child.py +++ b/bittensor/commands/stake/set_child.py @@ -23,7 +23,7 @@ import bittensor from .. import defaults, GetChildrenCommand # type: ignore -from ...utils.formatting import float_to_u16 +from ...utils.formatting import float_to_u64, normalize_u64_values console = bittensor.__console__ @@ -37,13 +37,13 @@ class SetChildCommand: Usage: Users can specify the amount or 'proportion' to delegate to a child hotkey (either by name or ``SS58`` address), - the user needs to have sufficient authority to make this call, and the sum of proportions cannot be greater than 1. + the user needs to have sufficient authority to make this call, and the sum of proportions cannot be greater than u16::MAX. The command prompts for confirmation before executing the set_child operation. Example usage:: - btcli stake set_child --child --hotkey --netuid 1 --proportion 0.3 + btcli stake set_child --child --hotkey --netuid 1 --proportion 19660 Note: This command is critical for users who wish to delegate child hotkeys among different neurons (hotkeys) on the network. @@ -220,7 +220,8 @@ def do_set_child_singular( ): try: # prepare values for emmit - proportion = float_to_u16(proportion) + proportion = float_to_u64(proportion) + proportion = normalize_u64_values([proportion])[0] call_module = "SubtensorModule" call_function = "set_child_singular" diff --git a/bittensor/commands/stake/set_children.py b/bittensor/commands/stake/set_children.py index 6948c4d822..0389a1c1c3 100644 --- a/bittensor/commands/stake/set_children.py +++ b/bittensor/commands/stake/set_children.py @@ -17,7 +17,7 @@ import argparse import re -from typing import Union +from typing import Union, List from rich.prompt import Confirm from numpy.typing import NDArray import numpy as np @@ -25,7 +25,11 @@ from typing import Tuple import bittensor from .. import defaults, GetChildrenCommand # type: ignore -from ...utils.formatting import float_to_u16 +from ...utils.formatting import ( + float_to_u64, + normalize_u64_values, + is_valid_ss58_address, +) console = bittensor.__console__ @@ -93,15 +97,16 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): # Parse from strings netuid = cli.config.netuid - proportions = np.array( - [float(x) for x in re.split(r"[ ,]+", cli.config.proportions)], - dtype=np.float32, - ) - children = np.array( - [str(x) for x in re.split(r"[ ,]+", cli.config.children)], dtype=str - ) + proportions = [float(x) for x in re.split(r"[ ,]+", cli.config.proportions)] + children = [str(x) for x in re.split(r"[ ,]+", cli.config.children)] - total_proposed = np.sum(proportions) + current_proportions + # Validate children SS58 addresses + for child in children: + if not is_valid_ss58_address(child): + console.print(f":cross_mark:[red] Invalid SS58 address: {child}[/red]") + return + + total_proposed = sum(proportions) + current_proportions if total_proposed > 1: raise ValueError( f":cross_mark:[red] The sum of all proportions cannot be greater than 1. Proposed sum of proportions is {total_proposed}[/red]" @@ -181,7 +186,7 @@ def do_set_children_multiple( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", hotkey: str, - children: Union[NDArray[str], list], + children: List[str], netuid: int, proportions: Union[NDArray[np.float32], list], wait_for_inclusion: bool = True, @@ -198,7 +203,7 @@ def do_set_children_multiple( Bittensor wallet object. hotkey (str): Parent hotkey. - children (np.ndarray): + children (List[str]): Children hotkeys. netuid (int): Unique identifier of for the subnet. @@ -241,11 +246,14 @@ def do_set_children_multiple( else proportions ) - # Convert each proportion value to u16 + # Convert each proportion value to u64 proportions_val = [ - float_to_u16(proportion) for proportion in proportions_val + float_to_u64(proportion) for proportion in proportions_val ] + # Normalize the u64 values to ensure their sum equals u64::MAX + proportions_val = normalize_u64_values(proportions_val) + children_with_proportions = list(zip(children, proportions_val)) call_module = "SubtensorModule" diff --git a/bittensor/utils/formatting.py b/bittensor/utils/formatting.py index 8bc433b5de..86769559d8 100644 --- a/bittensor/utils/formatting.py +++ b/bittensor/utils/formatting.py @@ -1,4 +1,7 @@ import math +from typing import List + +import bittensor def get_human_readable(num, suffix="H"): @@ -40,3 +43,73 @@ def u16_to_float(value): # Calculate the float representation u16_max = 65535 return value / u16_max + + +def float_to_u64(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") + + # Convert the float to a u64 value + return int(value * (2**64 - 1)) + + +def u64_to_float(value): + u64_max = 2**64 - 1 + # Allow for a small margin of error (e.g., 1) to account for potential rounding issues + if not (0 <= value <= u64_max + 1): + raise ValueError( + f"Input value ({value}) must be between 0 and {u64_max} (2^64 - 1)" + ) + return min(value / u64_max, 1.0) # Ensure the result is never greater than 1.0 + + +def normalize_u64_values(values: List[int]) -> List[int]: + """ + Normalize a list of u64 values so that their sum equals u64::MAX (2^64 - 1). + """ + if not values: + raise ValueError("Input list cannot be empty") + + if any(v < 0 for v in values): + raise ValueError("Input values must be non-negative") + + total = sum(values) + if total == 0: + raise ValueError("Sum of input values cannot be zero") + + u64_max = 2**64 - 1 + normalized = [int((v / total) * u64_max) for v in values] + + # Adjust values to ensure sum is exactly u64::MAX + current_sum = sum(normalized) + diff = u64_max - current_sum + + for i in range(abs(diff)): + if diff > 0: + normalized[i % len(normalized)] += 1 + else: + normalized[i % len(normalized)] = max( + 0, normalized[i % len(normalized)] - 1 + ) + + # Final check and adjustment + final_sum = sum(normalized) + if final_sum > u64_max: + normalized[-1] -= final_sum - u64_max + + assert ( + sum(normalized) == u64_max + ), f"Sum of normalized values ({sum(normalized)}) is not equal to u64::MAX ({u64_max})" + + return normalized + + +def is_valid_ss58_address(address: str) -> bool: + """ + Validate that the hotkey address input str is a valid ss58 address. + """ + try: + return bittensor.utils.ss58.is_valid_ss58_address(address) + except: + return False diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 7afb6b448f..c356541f2c 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -32,7 +32,7 @@ def local_chain(request): 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"fast_blocks={param}" + args = "" if param is None else f"{param}" # compile commands to send to process cmds = shlex.split(f"{script_path} {args}") diff --git a/tests/e2e_tests/subcommands/stake/test_get_child_hotkeys.py b/tests/e2e_tests/subcommands/stake/test_get_child_hotkeys.py index a7fa3364d9..f35701e16c 100644 --- a/tests/e2e_tests/subcommands/stake/test_get_child_hotkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_get_child_hotkeys.py @@ -121,6 +121,6 @@ def test_get_children_info(local_chain, capsys): output = capsys.readouterr().out # Assert table shows 1 child key with its data assert ( - "Total ( 1) | Total ( 1) | Total ( 0.299992) | Total ( 0.0000) | Avg ( \n0.0000) | Avg ( 0.0000) | Total ( 0.179995)" + "Total ( 1) | Total ( 1) | Total ( 1.000000) | Total (2147483648.0000) | Avg (\n0.0000) | Avg ( 0.0000) | Total ( 0.179995)" in output ) diff --git a/tests/e2e_tests/subcommands/stake/test_set_revoke_child_hotkeys.py b/tests/e2e_tests/subcommands/stake/test_set_revoke_child_hotkeys.py index f3eb2e3155..189349ce4c 100644 --- a/tests/e2e_tests/subcommands/stake/test_set_revoke_child_hotkeys.py +++ b/tests/e2e_tests/subcommands/stake/test_set_revoke_child_hotkeys.py @@ -96,7 +96,9 @@ def test_set_revoke_child(local_chain, capsys): ) subtensor = bittensor.subtensor(network="ws://localhost:9945") - assert len(subtensor.get_children_info(netuid=1)) == 1, "failed to set child hotkey" + assert ( + len(subtensor.get_children_info(netuid=1)[alice_keypair.ss58_address]) == 1 + ), "failed to set child hotkey" output = capsys.readouterr().out assert "✅ Finalized" in output @@ -147,7 +149,7 @@ def test_set_revoke_children(local_chain, capsys): # Register Bob bob_keypair, bob_exec_command, bob_wallet = setup_wallet("//Bob") - dan_keypair, dan_exec_command, dan_wallet = setup_wallet("//Dan") + eve_keypair, eve_exec_command, eve_wallet = setup_wallet("//Eve") # Register Alice neuron to the subnet alice_exec_command( @@ -183,7 +185,7 @@ def test_set_revoke_children(local_chain, capsys): ], ) - dan_exec_command( + eve_exec_command( RegisterCommand, [ "s", @@ -209,11 +211,11 @@ def test_set_revoke_children(local_chain, capsys): "--netuid", "1", "--children", - str(bob_keypair.ss58_address) + ", " + str(dan_keypair.ss58_address), + str(bob_keypair.ss58_address) + "," + str(eve_keypair.ss58_address), "--hotkey", - alice_wallet.hotkey_str, + str(alice_keypair.ss58_address), "--proportion", - "0.3, 0.5", + "0.3, 0.4", "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -221,8 +223,9 @@ def test_set_revoke_children(local_chain, capsys): ], ) + subtensor = bittensor.subtensor(network="ws://localhost:9945") assert ( - len(subtensor.get_children_info(netuid=1)) == 2 + len(subtensor.get_children_info(netuid=1)[alice_keypair.ss58_address]) == 2 ), "failed to set children hotkeys" output = capsys.readouterr().out @@ -238,9 +241,9 @@ def test_set_revoke_children(local_chain, capsys): "--netuid", "1", "--children", - str(bob_keypair.ss58_address) + ", " + str(dan_keypair.ss58_address), + str(bob_keypair.ss58_address) + "," + str(eve_keypair.ss58_address), "--hotkey", - alice_wallet.hotkey_str, + str(alice_keypair.ss58_address), "--wait_for_inclusion", "True", "--wait_for_finalization", @@ -248,6 +251,7 @@ def test_set_revoke_children(local_chain, capsys): ], ) + subtensor = bittensor.subtensor(network="ws://localhost:9945") assert ( subtensor.get_children_info(netuid=1) == [] ), "failed to revoke children hotkeys" diff --git a/tests/e2e_tests/subcommands/wallet/test_faucet.py b/tests/e2e_tests/subcommands/wallet/test_faucet.py index bb51bd9167..f4fea3ab9c 100644 --- a/tests/e2e_tests/subcommands/wallet/test_faucet.py +++ b/tests/e2e_tests/subcommands/wallet/test_faucet.py @@ -12,7 +12,6 @@ ) -@pytest.mark.skip @pytest.mark.parametrize("local_chain", [False], indirect=True) def test_faucet(local_chain): # Register root as Alice