diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index 16e68c254..359b08348 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -6188,6 +6188,12 @@ def stake_set_children( network: Optional[list[str]] = Options.network, netuid: Optional[int] = Options.netuid_not_req, all_netuids: bool = Options.all_netuids, + parent_hotkey: Optional[str] = typer.Option( + None, + "--parent-hotkey", + help="Parent hotkey SS58 to manage (defaults to the selected wallet hotkey).", + prompt=False, + ), proportions: list[float] = typer.Option( [], "--proportions", @@ -6247,6 +6253,11 @@ def stake_set_children( ask_for=[WO.NAME, WO.HOTKEY], validate=WV.WALLET_AND_HOTKEY, ) + + if parent_hotkey is not None and not is_valid_ss58_address(parent_hotkey): + print_error(f"Invalid SS58 address for --parent-hotkey: {parent_hotkey}") + raise typer.Exit() + logger.debug( "args:\n" f"network: {network}\n" @@ -6264,6 +6275,7 @@ def stake_set_children( netuid=netuid, children=children, proportions=proportions, + hotkey=parent_hotkey, wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, prompt=prompt, diff --git a/bittensor_cli/src/commands/stake/children_hotkeys.py b/bittensor_cli/src/commands/stake/children_hotkeys.py index 69d4083e4..6db01e877 100644 --- a/bittensor_cli/src/commands/stake/children_hotkeys.py +++ b/bittensor_cli/src/commands/stake/children_hotkeys.py @@ -1,6 +1,6 @@ import asyncio import json -from typing import Optional +from typing import Optional, Any from bittensor_wallet import Wallet from rich.prompt import IntPrompt, FloatPrompt @@ -499,10 +499,14 @@ async def _render_table( ) if not success: print_error(f"Failed to get children from subtensor: {err_mg}") + + # Always render the table, even if there are no children if children: netuid_children_tuples = [(netuid, children)] - await _render_table(get_hotkey_pub_ss58(wallet), netuid_children_tuples) + else: + netuid_children_tuples = [] + await _render_table(get_hotkey_pub_ss58(wallet), netuid_children_tuples) return children @@ -512,102 +516,94 @@ async def set_children( children: list[str], proportions: list[float], netuid: Optional[int] = None, + hotkey: Optional[str] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, prompt: bool = True, json_output: bool = False, proxy: Optional[str] = None, ): - """Set children hotkeys.""" - # TODO holy shit I hate this. It needs to be rewritten. + """ + Set children hotkeys with proportions for a parent hotkey on specified subnet(s). + + Args: + wallet: Wallet containing the coldkey for signing transactions. + subtensor: SubtensorInterface instance for blockchain interaction. + children: List of child hotkey SS58 addresses. + proportions: List of stake proportions (floats between 0 and 1). + netuid: Optional specific subnet ID. If None, operates on ALL non-root subnets. + hotkey: Optional parent hotkey SS58 address. If None, uses wallet's hotkey. + wait_for_inclusion: Wait for transaction to be included in a block. + wait_for_finalization: Wait for transaction to be finalized. + prompt: Prompt user for confirmation before submitting transactions. + json_output: Output results as JSON instead of formatted text. + proxy: Optional proxy SS58 address for transaction submission. + """ + parent_hotkey = hotkey if hotkey is not None else get_hotkey_pub_ss58(wallet) + # Validate children SS58 addresses - # TODO check to see if this should be allowed to be specified by user instead of pulling from wallet - hotkey = get_hotkey_pub_ss58(wallet) for child in children: if not is_valid_ss58_address(child): - print_error(f"Invalid SS58 address: {child}") + msg = f"Invalid SS58 address: {child}" + print_error(msg) + if json_output: + json_console.print(json.dumps({"success": False, "message": msg})) return - if child == hotkey: - print_error("Cannot set yourself as a child.") + if child == parent_hotkey: + msg = "Cannot set yourself as a child." + print_error(msg) + if json_output: + json_console.print(json.dumps({"success": False, "message": msg})) return - total_proposed = sum(proportions) - if total_proposed > 1: - raise ValueError( - f"Invalid proportion: The sum of all proportions cannot be greater than 1. " - f"Proposed sum of proportions is {total_proposed}." - ) children_with_proportions = list(zip(proportions, children)) - successes = {} + + # Determine netuids if netuid is not None: + netuids = [netuid] + else: + all_netuids = await subtensor.get_all_subnet_netuids() + netuids = [n for n in all_netuids if n != 0] + + # Execute operations + dict_output = {} + for netuid_ in netuids: success, message, ext_id = await set_children_extrinsic( subtensor=subtensor, wallet=wallet, - netuid=netuid, - hotkey=hotkey, + netuid=netuid_, + hotkey=parent_hotkey, proxy=proxy, children_with_proportions=children_with_proportions, prompt=prompt, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - successes[netuid] = { + dict_output[netuid_] = { "success": success, - "error": message, "completion_block": None, + "error": message, "set_block": None, "extrinsic_identifier": ext_id, } - # Result + if success: - if wait_for_inclusion and wait_for_finalization: - current_block, completion_block = await get_childkey_completion_block( - subtensor, netuid - ) - successes[netuid]["completion_block"] = completion_block - successes[netuid]["set_block"] = current_block - console.print( - f"Your childkey request has been submitted. It will be completed around block {completion_block}. " - f"The current block is {current_block}" - ) - print_success("Set children hotkeys.") - else: - print_error(f"Unable to set children hotkeys. {message}") - else: - # set children on all subnets that parent is registered on - netuids = await subtensor.get_all_subnet_netuids() - for netuid_ in netuids: - if netuid_ == 0: # dont include root network - continue - console.print(f"Setting children on netuid {netuid_}.") - success, message, ext_id = await set_children_extrinsic( - subtensor=subtensor, - wallet=wallet, - netuid=netuid_, - hotkey=hotkey, - proxy=proxy, - children_with_proportions=children_with_proportions, - prompt=prompt, - wait_for_inclusion=True, - wait_for_finalization=False, - ) current_block, completion_block = await get_childkey_completion_block( subtensor, netuid_ ) - successes[netuid_] = { - "success": success, - "error": message, - "completion_block": completion_block, - "set_block": current_block, - "extrinsic_identifier": ext_id, - } + dict_output[netuid_]["set_block"] = current_block + dict_output[netuid_]["completion_block"] = completion_block console.print( - f"Your childkey request for netuid {netuid_} has been submitted. It will be completed around " - f"block {completion_block}. The current block is {current_block}." + f":white_heavy_check_mark: Your childkey request for netuid {netuid_} has been submitted. " + f"It will be completed around block {completion_block}. The current block is {current_block}" + ) + else: + print_error( + f"Failed to set children hotkeys for netuid {netuid_}: {message}" ) - print_success("Sent set children request for all subnets.") + if json_output: - json_console.print(json.dumps(successes)) + json_console.print(json.dumps(dict_output)) async def revoke_children( diff --git a/tests/e2e_tests/test_children_hotkeys.py b/tests/e2e_tests/test_children_hotkeys.py index f286012fd..d55833d76 100644 --- a/tests/e2e_tests/test_children_hotkeys.py +++ b/tests/e2e_tests/test_children_hotkeys.py @@ -1,15 +1,771 @@ +import json import pytest -from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface -from bittensor_cli.src.commands.stake.children_hotkeys import ( - get_childkey_completion_block, -) +""" +Verify commands: -@pytest.mark.asyncio -async def test_get_childkey_completion_block(local_chain): - async with SubtensorInterface("ws://127.0.0.1:9945") as subtensor: - current_block, completion_block = await get_childkey_completion_block( - subtensor, 1 +* btcli stake child get +* btcli stake child set +* btcli stake child revoke +""" + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +def test_set_children_single_child(local_chain, wallet_setup): + """ + Test setting a single child hotkey on a subnet. + + Steps: + 1. Create wallets for Alice (parent) and Bob (child) + 2. Create a subnet and register both Alice and Bob + 3. Start emissions on the subnet + 4. Add stake to Alice's hotkey + 5. Set Bob as a child hotkey with 50% proportion + 6. Verify children are set via get command + """ + print("Testing set_children with single child ๐Ÿงช") + # Create wallets for Alice and Bob + wallet_path_alice = "//Alice" + netuid = 2 + + # Create wallet for Alice + keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( + wallet_path_alice + ) + + # Register a subnet with sudo as Alice + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--subnet-name", + "Test Subnet", + "--repo", + "https://github.com/username/repo", + "--contact", + "alice@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "alice#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Created by Alice", + "--logo-url", + "https://testsubnet.com/logo.png", + "--no-prompt", + "--json-output", + "--no-mev-protection", + ], + ) + result_output = json.loads(result.stdout) + assert result_output["success"] is True + assert result_output["netuid"] == netuid + assert isinstance(result_output["extrinsic_identifier"], str) + + # Create wallet for Bob (child) + keypair_bob, wallet_bob, wallet_path_bob, exec_command_bob = wallet_setup("//Bob") + + # Register Bob on the subnet + register_bob_result = exec_command_bob( + command="subnets", + sub_command="register", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--netuid", + netuid, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + "โœ… Registered" in register_bob_result.stdout + or "โœ… Already Registered" in register_bob_result.stdout + ) + + # Start emissions on subnet + start_emission_result = exec_command_alice( + command="subnets", + sub_command="start", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + f"Successfully started subnet {netuid}'s emission schedule" + in start_emission_result.stdout + ) + + # Add stake to Alice's hotkey to enable V3 + add_stake_result = exec_command_alice( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--amount", + "1", + "--unsafe", + "--no-prompt", + "--era", + "144", + "--no-mev-protection", + ], + ) + assert "โœ… Finalized" in add_stake_result.stdout + + # Set Bob as a child hotkey with 50% proportion + set_children_result = exec_command_alice( + command="stake", + sub_command="child", + extra_args=[ + "set", + "--children", + wallet_bob.hotkey.ss58_address, + "--proportions", + "0.5", + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + "--json-output", + ], + ) + set_children_output = json.loads(set_children_result.stdout) + assert set_children_output[str(netuid)]["success"] is True + assert isinstance(set_children_output[str(netuid)]["extrinsic_identifier"], str) + # Note: Children changes are not immediate - they require waiting for completion_block + # The completion_block indicates when the change will take effect + assert set_children_output[str(netuid)]["completion_block"] is not None + assert set_children_output[str(netuid)]["set_block"] is not None + + print("โœ… Passed set_children with single child") + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +def test_set_children_multiple_proportions(local_chain, wallet_setup): + """ + Test setting multiple children hotkeys with different proportions. + + Steps: + 1. Create wallets for Alice (parent), Bob, Charlie, and Dave (children) + 2. Create a subnet and register all participants + 3. Start emissions on the subnet + 4. Add stake to Alice's hotkey + 5. Set multiple children with different proportions (Bob: 25%, Charlie: 35%, Dave: 20%) + 6. Verify the transaction succeeded + """ + print("Testing set_children with multiple proportions") + + wallet_path_alice = "//Alice" + netuid = 2 + + # Create wallet for Alice (parent) + keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( + wallet_path_alice + ) + + # Create subnet as Alice + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--subnet-name", + "Test Subnet", + "--repo", + "https://github.com/username/repo", + "--contact", + "alice@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "alice#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Created by Alice", + "--logo-url", + "https://testsubnet.com/logo.png", + "--no-prompt", + "--json-output", + "--no-mev-protection", + ], + ) + result_output = json.loads(result.stdout) + assert result_output["success"] is True + assert result_output["netuid"] == netuid + + # Create wallets for children + keypair_bob, wallet_bob, wallet_path_bob, exec_command_bob = wallet_setup("//Bob") + keypair_charlie, wallet_charlie, wallet_path_charlie, exec_command_charlie = ( + wallet_setup("//Charlie") + ) + keypair_dave, wallet_dave, wallet_path_dave, exec_command_dave = wallet_setup( + "//Dave" + ) + + # Register all children on the subnet + for wallet, wallet_path, exec_command in [ + (wallet_bob, wallet_path_bob, exec_command_bob), + (wallet_charlie, wallet_path_charlie, exec_command_charlie), + (wallet_dave, wallet_path_dave, exec_command_dave), + ]: + register_result = exec_command( + command="subnets", + sub_command="register", + extra_args=[ + "--wallet-path", + wallet_path, + "--wallet-name", + wallet.name, + "--hotkey", + wallet.hotkey_str, + "--netuid", + netuid, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + "โœ… Registered" in register_result.stdout + or "โœ… Already Registered" in register_result.stdout + ) + + # Start emissions on subnet + start_emission_result = exec_command_alice( + command="subnets", + sub_command="start", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + f"Successfully started subnet {netuid}'s emission schedule" + in start_emission_result.stdout + ) + + # Add stake to Alice's hotkey + add_stake_result = exec_command_alice( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--amount", + "1", + "--unsafe", + "--no-prompt", + "--era", + "144", + "--no-mev-protection", + ], + ) + assert "โœ… Finalized" in add_stake_result.stdout + + # Set multiple children with different proportions + # Bob: 25%, Charlie: 35%, Dave: 20% + # Note: Typer list options require repeating the flag for each value + set_children_result = exec_command_alice( + command="stake", + sub_command="child", + extra_args=[ + "set", + "--children", + wallet_bob.hotkey.ss58_address, + "--children", + wallet_charlie.hotkey.ss58_address, + "--children", + wallet_dave.hotkey.ss58_address, + "--proportions", + "0.25", + "--proportions", + "0.35", + "--proportions", + "0.20", + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + "--json-output", + ], + ) + + # Debug output if parsing fails + if not set_children_result.stdout.strip(): + print(f"stdout is empty") + print(f"stderr: {set_children_result.stderr}") + print(f"exit_code: {set_children_result.exit_code}") + pytest.fail( + f"set_children returned empty stdout. stderr: {set_children_result.stderr}" ) - assert (completion_block - current_block) >= 7200 + + try: + set_children_output = json.loads(set_children_result.stdout) + except json.JSONDecodeError as e: + print(f"Failed to parse JSON") + print(f"stdout: {set_children_result.stdout}") + print(f"stderr: {set_children_result.stderr}") + pytest.fail(f"Failed to parse JSON: {e}. stdout: {set_children_result.stdout}") + + assert set_children_output[str(netuid)]["success"] is True + assert isinstance(set_children_output[str(netuid)]["extrinsic_identifier"], str) + assert set_children_output[str(netuid)]["completion_block"] is not None + assert set_children_output[str(netuid)]["set_block"] is not None + + print("โœ… Passed set_children with multiple proportions") + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +def test_get_children_json_output(local_chain, wallet_setup): + """ + Test getting children with JSON output. + + Steps: + 1. Create subnet and set children + 2. Get children with --json-output flag + 3. Verify JSON structure and content + """ + print("Testing get_children with JSON output ๐Ÿงช") + wallet_path_alice = "//Alice" + netuid = 2 + + # Create wallet for Alice + keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( + wallet_path_alice + ) + keypair_bob, wallet_bob, wallet_path_bob, exec_command_bob = wallet_setup("//Bob") + + # Register a subnet with sudo as Alice + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--subnet-name", + "Test Subnet", + "--repo", + "https://github.com/username/repo", + "--contact", + "alice@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "alice#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Created by Alice", + "--logo-url", + "https://testsubnet.com/logo.png", + "--no-prompt", + "--json-output", + "--no-mev-protection", + ], + ) + result_output = json.loads(result.stdout) + assert result_output["success"] is True + assert result_output["netuid"] == netuid + assert isinstance(result_output["extrinsic_identifier"], str) + + # Register Bob + register_bob_result = exec_command_bob( + command="subnets", + sub_command="register", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--netuid", + netuid, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + "โœ… Registered" in register_bob_result.stdout + or "โœ… Already Registered" in register_bob_result.stdout + ) + + # Start emissions + start_emission_result = exec_command_alice( + command="subnets", + sub_command="start", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + f"Successfully started subnet {netuid}'s emission schedule" + in start_emission_result.stdout + ) + + # Add stake + add_stake_result = exec_command_alice( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--amount", + "1", + "--unsafe", + "--no-prompt", + "--era", + "144", + "--no-mev-protection", + ], + ) + assert "โœ… Finalized" in add_stake_result.stdout + + # Set children + set_children_result = exec_command_alice( + command="stake", + sub_command="child", + extra_args=[ + "set", + "--children", + wallet_bob.hotkey.ss58_address, + "--proportions", + "0.5", + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + "--json-output", + ], + ) + set_children_output = json.loads(set_children_result.stdout) + assert set_children_output[str(netuid)]["success"] is True + # Note: Children changes are not immediate - they require waiting for completion_block + assert set_children_output[str(netuid)]["completion_block"] is not None + + # Get children with JSON output + # Note: Children won't be visible immediately since they require waiting for completion_block + get_children_result = exec_command_alice( + command="stake", + sub_command="child", + extra_args=[ + "get", + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--json-output", + ], + ) + get_children_output = json.loads(get_children_result.stdout) + # Verify JSON structure - should be a list (may be empty since children aren't immediately active) + assert isinstance(get_children_output, list) + # If there are children, verify structure + for child in get_children_output: + assert isinstance(child, (list, tuple)) + assert len(child) >= 2 + # First element should be proportion (float or int) + assert isinstance(child[0], (float, int)) + # Second element should be SS58 address (string) + assert isinstance(child[1], str) + assert len(child[1]) > 0 + + print("โœ… Passed get_children with JSON output") + + +@pytest.mark.parametrize("local_chain", [False], indirect=True) +def test_get_children_non_json_output(local_chain, wallet_setup): + """ + Test getting children without JSON output (table format). + + Steps: + 1. Create subnet and set children + 2. Get children without --json-output flag + 3. Verify output contains expected information + """ + print("Testing get_children without JSON output ๐Ÿงช") + wallet_path_alice = "//Alice" + netuid = 2 + + # Create wallet for Alice + keypair_alice, wallet_alice, wallet_path_alice, exec_command_alice = wallet_setup( + wallet_path_alice + ) + keypair_bob, wallet_bob, wallet_path_bob, exec_command_bob = wallet_setup("//Bob") + + # Register a subnet with sudo as Alice + result = exec_command_alice( + command="subnets", + sub_command="create", + extra_args=[ + "--wallet-path", + wallet_path_alice, + "--network", + "ws://127.0.0.1:9945", + "--wallet-name", + wallet_alice.name, + "--wallet-hotkey", + wallet_alice.hotkey_str, + "--subnet-name", + "Test Subnet", + "--repo", + "https://github.com/username/repo", + "--contact", + "alice@opentensor.dev", + "--url", + "https://testsubnet.com", + "--discord", + "alice#1234", + "--description", + "A test subnet for e2e testing", + "--additional-info", + "Created by Alice", + "--logo-url", + "https://testsubnet.com/logo.png", + "--no-prompt", + "--json-output", + "--no-mev-protection", + ], + ) + result_output = json.loads(result.stdout) + assert result_output["success"] is True + assert result_output["netuid"] == netuid + assert isinstance(result_output["extrinsic_identifier"], str) + + # Register Bob + register_bob_result = exec_command_bob( + command="subnets", + sub_command="register", + extra_args=[ + "--wallet-path", + wallet_path_bob, + "--wallet-name", + wallet_bob.name, + "--hotkey", + wallet_bob.hotkey_str, + "--netuid", + netuid, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + "โœ… Registered" in register_bob_result.stdout + or "โœ… Already Registered" in register_bob_result.stdout + ) + + # Start emissions + start_emission_result = exec_command_alice( + command="subnets", + sub_command="start", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + ], + ) + assert ( + f"Successfully started subnet {netuid}'s emission schedule" + in start_emission_result.stdout + ) + + # Add stake + add_stake_result = exec_command_alice( + command="stake", + sub_command="add", + extra_args=[ + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--amount", + "1", + "--unsafe", + "--no-prompt", + "--era", + "144", + "--no-mev-protection", + ], + ) + assert "โœ… Finalized" in add_stake_result.stdout + + # Set children + set_children_result = exec_command_alice( + command="stake", + sub_command="child", + extra_args=[ + "set", + "--children", + wallet_bob.hotkey.ss58_address, + "--proportions", + "0.5", + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + "--no-prompt", + "--json-output", + ], + ) + set_children_output = json.loads(set_children_result.stdout) + assert set_children_output[str(netuid)]["success"] is True + # Note: Children changes are not immediate - they require waiting for completion_block + assert set_children_output[str(netuid)]["completion_block"] is not None + + # Get children without JSON output (should show table) + # Note: Children won't be visible immediately since they require waiting for completion_block + get_children_result = exec_command_alice( + command="stake", + sub_command="child", + extra_args=[ + "get", + "--netuid", + netuid, + "--wallet-path", + wallet_path_alice, + "--wallet-name", + wallet_alice.name, + "--hotkey", + wallet_alice.hotkey_str, + "--network", + "ws://127.0.0.1:9945", + ], + ) + # Should have output (table format or message about no children) + assert len(get_children_result.stdout) > 0 + + print("โœ… Passed get_children without JSON output")