From f50e4ac3c4b7b8ca1edaa85a89365a0ba35f00c4 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Jun 2025 18:39:37 -0700 Subject: [PATCH 01/12] move `set_children_extrinsic` to their extrinsic modules --- bittensor/core/async_subtensor.py | 42 +++------ bittensor/core/extrinsics/asyncex/children.py | 84 +++++++++++++++++ bittensor/core/extrinsics/children.py | 91 +++++++++++++++++++ bittensor/core/subtensor.py | 69 ++++---------- 4 files changed, 208 insertions(+), 78 deletions(-) create mode 100644 bittensor/core/extrinsics/asyncex/children.py create mode 100644 bittensor/core/extrinsics/children.py diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f732502c18..52f9026478 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -44,6 +44,10 @@ swap_stake_extrinsic, move_stake_extrinsic, ) +from bittensor.core.extrinsics.asyncex.children import ( + root_set_pending_childkey_cooldown_extrinsic, + set_children_extrinsic, +) from bittensor.core.extrinsics.asyncex.registration import ( burned_register_extrinsic, register_extrinsic, @@ -78,19 +82,18 @@ set_weights_extrinsic, reveal_weights_extrinsic, ) +from bittensor.core.extrinsics.children import set_children_extrinsic from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import version_as_int, TYPE_REGISTRY from bittensor.core.types import ParamWithTypes, SubtensorMixin from bittensor.utils import ( Certificate, decode_hex_identity_dict, - float_to_u64, format_error_message, is_valid_ss58_address, torch, u16_normalized_float, u64_normalized_float, - unlock_key, ) from bittensor.utils.balance import ( Balance, @@ -3998,33 +4001,14 @@ async def set_children( bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. """ - - unlock = unlock_key(wallet, raise_error=raise_error) - - if not unlock.success: - return False, unlock.message - - call = await self.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - float_to_u64(proportion), - child_hotkey, - ) - for proportion, child_hotkey in children - ], - "hotkey": hotkey, - "netuid": netuid, - }, - ) - - return await self.sign_and_send_extrinsic( - call, - wallet, - wait_for_inclusion, - wait_for_finalization, + return await set_children_extrinsic( + subtensor=self, + wallet=wallet, + hotkey=hotkey, + netuid=netuid, + children=children, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, raise_error=raise_error, period=period, ) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py new file mode 100644 index 0000000000..56a86cf9a0 --- /dev/null +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -0,0 +1,84 @@ +from typing import TYPE_CHECKING, Optional +from bittensor.utils import float_to_u64, unlock_key + +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.core.async_subtensor import AsyncSubtensor + + +async def set_children_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + hotkey: str, + netuid: int, + children: list[tuple[float, str]], + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + raise_error: bool = False, + period: Optional[int] = None, +): + """ + Allows a coldkey to set children-keys. + + Arguments: + subtensor: bittensor subtensor. + wallet: bittensor wallet instance. + hotkey: The ``SS58`` address of the neuron's hotkey. + netuid: The netuid value. + children: A list of children with their proportions. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You can + think of it as an expiration date for the transaction. + + Returns: + tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the operation, + and the second element is a message providing additional information. + + Raises: + DuplicateChild: There are duplicates in the list of children. + InvalidChild: Child is the hotkey. + NonAssociatedColdKey: The coldkey does not own the hotkey or the child is the same as the hotkey. + NotEnoughStakeToSetChildkeys: Parent key doesn't have minimum own stake. + ProportionOverflow: The sum of the proportions does exceed uint64. + RegistrationNotPermittedOnRootSubnet: Attempting to register a child on the root network. + SubNetworkDoesNotExist: Attempting to register to a non-existent network. + TooManyChildren: Too many children in request. + TxRateLimitExceeded: Hotkey hit the rate limit. + bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. + bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. + """ + unlock = unlock_key(wallet, raise_error=raise_error) + + if not unlock.success: + return False, unlock.message + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_children", + call_params={ + "children": [ + ( + float_to_u64(proportion), + child_hotkey, + ) + for proportion, child_hotkey in children + ], + "hotkey": hotkey, + "netuid": netuid, + }, + ) + + return await subtensor.sign_and_send_extrinsic( + call, + wallet, + wait_for_inclusion, + wait_for_finalization, + raise_error=raise_error, + period=period, + ) + + +async def root_set_pending_childkey_cooldown_extrinsic(): ... diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py new file mode 100644 index 0000000000..3998d5adbe --- /dev/null +++ b/bittensor/core/extrinsics/children.py @@ -0,0 +1,91 @@ +from typing import TYPE_CHECKING, Optional +from bittensor.utils import float_to_u64, unlock_key + +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.core.subtensor import Subtensor + + +def set_children_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + hotkey: str, + netuid: int, + children: list[tuple[float, str]], + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + raise_error: bool = False, + period: Optional[int] = None, +): + """ + Allows a coldkey to set children-keys. + + Arguments: + subtensor: bittensor subtensor. + wallet: bittensor wallet instance. + hotkey: The ``SS58`` address of the neuron's hotkey. + netuid: The netuid value. + children: A list of children with their proportions. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + period: The number of blocks during which the transaction will remain valid after it's submitted. If the + transaction is not included in a block within that number of blocks, it will expire and be rejected. You can + think of it as an expiration date for the transaction. + + Returns: + tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the operation, + and the second element is a message providing additional information. + + Raises: + DuplicateChild: There are duplicates in the list of children. + InvalidChild: Child is the hotkey. + NonAssociatedColdKey: The coldkey does not own the hotkey or the child is the same as the hotkey. + NotEnoughStakeToSetChildkeys: Parent key doesn't have minimum own stake. + ProportionOverflow: The sum of the proportions does exceed uint64. + RegistrationNotPermittedOnRootSubnet: Attempting to register a child on the root network. + SubNetworkDoesNotExist: Attempting to register to a non-existent network. + TooManyChildren: Too many children in request. + TxRateLimitExceeded: Hotkey hit the rate limit. + bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. + bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. + """ + unlock = unlock_key(wallet, raise_error=raise_error) + + if not unlock.success: + return False, unlock.message + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_children", + call_params={ + "children": [ + ( + float_to_u64(proportion), + child_hotkey, + ) + for proportion, child_hotkey in children + ], + "hotkey": hotkey, + "netuid": netuid, + }, + ) + + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + raise_error=raise_error, + period=period, + ) + + +def root_set_pending_childkey_cooldown_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + cooldown: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, + period: Optional[int] = None, +) -> tuple[bool, str]: ... diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b87f1f6de0..57eb04e62f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -36,6 +36,10 @@ ) from bittensor.core.config import Config from bittensor.core.errors import ChainError +from bittensor.core.extrinsics.children import ( + set_children_extrinsic, + root_set_pending_childkey_cooldown_extrinsic, +) from bittensor.core.extrinsics.commit_reveal import commit_reveal_v3_extrinsic from bittensor.core.extrinsics.commit_weights import ( commit_weights_extrinsic, @@ -86,13 +90,11 @@ from bittensor.utils import ( Certificate, decode_hex_identity_dict, - float_to_u64, format_error_message, is_valid_ss58_address, torch, u16_normalized_float, u64_normalized_float, - unlock_key, ) from bittensor.utils.balance import ( Balance, @@ -3207,61 +3209,30 @@ def set_children( Allows a coldkey to set children-keys. Arguments: - wallet (bittensor_wallet.Wallet): bittensor wallet instance. - hotkey (str): The ``SS58`` address of the neuron's hotkey. - netuid (int): The netuid value. - children (list[tuple[float, str]]): A list of children with their proportions. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. + wallet: bittensor wallet instance. + hotkey: The ``SS58`` address of the neuron's hotkey. + netuid: The netuid value. + children: A list of children with their proportions. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + period: The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. Returns: tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + operation, and the second element is a message providing additional information. - Raises: - DuplicateChild: There are duplicates in the list of children. - InvalidChild: Child is the hotkey. - NonAssociatedColdKey: The coldkey does not own the hotkey or the child is the same as the hotkey. - NotEnoughStakeToSetChildkeys: Parent key doesn't have minimum own stake. - ProportionOverflow: The sum of the proportions does exceed uint64. - RegistrationNotPermittedOnRootSubnet: Attempting to register a child on the root network. - SubNetworkDoesNotExist: Attempting to register to a non-existent network. - TooManyChildren: Too many children in request. - TxRateLimitExceeded: Hotkey hit the rate limit. - bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. - bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. """ - - unlock = unlock_key(wallet, raise_error=raise_error) - - if not unlock.success: - return False, unlock.message - - call = self.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - float_to_u64(proportion), - child_hotkey, - ) - for proportion, child_hotkey in children - ], - "hotkey": hotkey, - "netuid": netuid, - }, - ) - - return self.sign_and_send_extrinsic( - call, - wallet, - wait_for_inclusion, - wait_for_finalization, + return set_children_extrinsic( + subtensor=self, + wallet=wallet, + hotkey=hotkey, + netuid=netuid, + children=children, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, raise_error=raise_error, period=period, ) From 74b330ff39d7052938f8759f16ce89099ec64be2 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Jun 2025 21:50:54 -0700 Subject: [PATCH 02/12] add `root_set_pending_childkey_cooldown_extrinsic` --- bittensor/core/extrinsics/asyncex/children.py | 36 ++++++++++++++++++- bittensor/core/extrinsics/children.py | 29 ++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 56a86cf9a0..9ce18f8918 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -81,4 +81,38 @@ async def set_children_extrinsic( ) -async def root_set_pending_childkey_cooldown_extrinsic(): ... +async def root_set_pending_childkey_cooldown_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + cooldown: int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, + period: Optional[int] = None, +) -> tuple[bool, str]: + """ + Allows a coldkey to set children-keys. + """ + unlock = unlock_key(wallet) + + if not unlock.success: + return False, unlock.message + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_pending_childkey_cooldown", + call_params={"cooldown": cooldown}, + ) + + sudo_call = await subtensor.substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={"call": call}, + ) + + return await subtensor.sign_and_send_extrinsic( + call=sudo_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + ) diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 3998d5adbe..3e3111e846 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -88,4 +88,31 @@ def root_set_pending_childkey_cooldown_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, -) -> tuple[bool, str]: ... +) -> tuple[bool, str]: + """ + Allows a coldkey to set children-keys. + """ + unlock = unlock_key(wallet) + + if not unlock.success: + return False, unlock.message + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_pending_childkey_cooldown", + call_params={"cooldown": cooldown}, + ) + + sudo_call = subtensor.substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={"call": call}, + ) + + return subtensor.sign_and_send_extrinsic( + call=sudo_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + ) From 9eb028d64fe43f2c5c8d14179740f537063aec75 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Jun 2025 21:51:20 -0700 Subject: [PATCH 03/12] update call in subtensor --- bittensor/core/async_subtensor.py | 35 +++++++++++++++++++++++++++++++ bittensor/core/subtensor.py | 35 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 52f9026478..53552c4b12 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3881,6 +3881,41 @@ async def reveal_weights( return success, message + async def root_set_pending_childkey_cooldown( + self, + wallet: "Wallet", + cooldown: int, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + period: Optional[int] = None, + ) -> tuple[bool, str]: + """Sets the pending childkey cooldown. + + Arguments: + wallet: bittensor wallet instance. + cooldown: the number of blocks to setting pending childkey cooldown. + wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``False``. + wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is + ``False``. + period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + submitted. If the transaction is not included in a block within that number of blocks, it will expire + and be rejected. You can think of it as an expiration date for the transaction. + + Returns: + tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the + operation, and the second element is a message providing additional information. + + Note: This operation can only be successfully performed if your wallet has root privileges. + """ + return await root_set_pending_childkey_cooldown_extrinsic( + subtensor=self, + wallet=wallet, + cooldown=cooldown, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + ) + # TODO: remove `block_hash` argument async def root_register( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 57eb04e62f..788130f4b1 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3152,6 +3152,41 @@ def root_register( period=period, ) + def root_set_pending_childkey_cooldown( + self, + wallet: "Wallet", + cooldown: int, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + period: Optional[int] = None, + ) -> tuple[bool, str]: + """Sets the pending childkey cooldown. + + Arguments: + wallet: bittensor wallet instance. + cooldown: the number of blocks to setting pending childkey cooldown. + wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``False``. + wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is + ``False``. + period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + submitted. If the transaction is not included in a block within that number of blocks, it will expire + and be rejected. You can think of it as an expiration date for the transaction. + + Returns: + tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the + operation, and the second element is a message providing additional information. + + Note: This operation can only be successfully performed if your wallet has root privileges. + """ + return root_set_pending_childkey_cooldown_extrinsic( + subtensor=self, + wallet=wallet, + cooldown=cooldown, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + ) + def root_set_weights( self, wallet: "Wallet", From c87924805cd10eb4358baf8e11db8fada3e4beca Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Jun 2025 21:51:33 -0700 Subject: [PATCH 04/12] add call in SubtensorApi --- bittensor/core/subtensor_api/extrinsics.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bittensor/core/subtensor_api/extrinsics.py b/bittensor/core/subtensor_api/extrinsics.py index 0ff4439201..0096fe8c8c 100644 --- a/bittensor/core/subtensor_api/extrinsics.py +++ b/bittensor/core/subtensor_api/extrinsics.py @@ -17,6 +17,9 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.reveal_weights = subtensor.reveal_weights self.root_register = subtensor.root_register self.root_set_weights = subtensor.root_set_weights + self.root_set_pending_childkey_cooldown = ( + subtensor.root_set_pending_childkey_cooldown + ) self.set_children = subtensor.set_children self.set_subnet_identity = subtensor.set_subnet_identity self.set_weights = subtensor.set_weights From f03172d8498d3be5948ddeaa2128f99088899903 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Jun 2025 21:51:43 -0700 Subject: [PATCH 05/12] fix e2e test --- tests/e2e_tests/test_hotkeys.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 84f612d6a2..918335f26a 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -17,6 +17,7 @@ SET_CHILDREN_RATE_LIMIT = 15 +ROOT_COOLDOWN = 15 # blocks def test_hotkeys(subtensor, alice_wallet, dave_wallet): @@ -87,6 +88,13 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w dave_subnet_netuid = subtensor.get_total_subnets() # 2 set_tempo = 10 # affect to non-fast-blocks mode + # Set cooldown + success, message = subtensor.extrinsics.root_set_pending_childkey_cooldown( + wallet=alice_wallet, cooldown=ROOT_COOLDOWN + ) + assert success, f"Call `root_set_pending_childkey_cooldown` failed: {message}" + assert message == "" + assert subtensor.register_subnet(dave_wallet, True, True) assert subtensor.subnet_exists(dave_subnet_netuid), ( f"Subnet #{dave_subnet_netuid} does not exist." From 4f474fb7a9bbdd2014f43233f11375bf2a76a6ab Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 16 Jun 2025 21:53:20 -0700 Subject: [PATCH 06/12] utils --- bittensor/core/subtensor_api/utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 0ddd28bcc7..f0f2ded013 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -133,6 +133,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.register_subnet = subtensor._subtensor.register_subnet subtensor.reveal_weights = subtensor._subtensor.reveal_weights subtensor.root_register = subtensor._subtensor.root_register + subtensor.root_set_pending_childkey_cooldown = ( + subtensor._subtensor.root_set_pending_childkey_cooldown + ) subtensor.root_set_weights = subtensor._subtensor.root_set_weights subtensor.serve_axon = subtensor._subtensor.serve_axon subtensor.set_children = subtensor._subtensor.set_children From 92bac714c4355aa57095285dee926eff9ff2e842 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Jun 2025 09:12:06 -0700 Subject: [PATCH 07/12] added unit tests for `subtensor.set_children` calls --- bittensor/core/extrinsics/asyncex/children.py | 8 +-- tests/unit_tests/test_async_subtensor.py | 52 +++++++++---------- tests/unit_tests/test_subtensor.py | 37 +++++++++++++ 3 files changed, 67 insertions(+), 30 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 9ce18f8918..474fbb3f54 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -72,10 +72,10 @@ async def set_children_extrinsic( ) return await subtensor.sign_and_send_extrinsic( - call, - wallet, - wait_for_inclusion, - wait_for_finalization, + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, raise_error=raise_error, period=period, ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 12c6b6e123..97554b4468 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2642,41 +2642,41 @@ async def test_register_success(subtensor, fake_wallet, mocker): @pytest.mark.asyncio -async def test_set_children(mock_substrate, subtensor, fake_wallet, mocker): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - is_success=mocker.AsyncMock(return_value=True)(), +async def test_set_children(subtensor, fake_wallet, mocker): + """Tests set_children extrinsic calls properly.""" + # Preps + mocked_set_children_extrinsic = mocker.AsyncMock() + mocker.patch.object( + async_subtensor, "set_children_extrinsic", mocked_set_children_extrinsic ) + fake_children = [ + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ] - await subtensor.set_children( + # Call + result = await subtensor.set_children( fake_wallet, fake_wallet.hotkey.ss58_address, netuid=1, - children=[ - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ], + children=fake_children, ) - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - U64_MAX, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ) - ], - "hotkey": fake_wallet.hotkey.ss58_address, - "netuid": 1, - }, - wait_for_inclusion=True, + # Asserts + mocked_set_children_extrinsic.assert_awaited_once_with( + subtensor=subtensor, + wallet=fake_wallet, + hotkey=fake_wallet.hotkey.ss58_address, + netuid=1, + children=fake_children, wait_for_finalization=True, + wait_for_inclusion=True, + raise_error=False, + period=None, ) + assert result == mocked_set_children_extrinsic.return_value @pytest.mark.asyncio diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index bb73ec2f13..75e7a7950f 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3811,3 +3811,40 @@ def test_get_parents_no_parents(subtensor, mocker): params=[fake_hotkey, fake_netuid], ) assert result == [] + + +def test_set_children(subtensor, fake_wallet, mocker): + """Tests set_children extrinsic calls properly.""" + # Preps + mocked_set_children_extrinsic = mocker.Mock() + mocker.patch.object( + subtensor_module, "set_children_extrinsic", mocked_set_children_extrinsic + ) + fake_children = [ + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ] + + # Call + result = subtensor.set_children( + fake_wallet, + fake_wallet.hotkey.ss58_address, + netuid=1, + children=fake_children, + ) + + # Asserts + mocked_set_children_extrinsic.assert_called_once_with( + subtensor=subtensor, + wallet=fake_wallet, + hotkey=fake_wallet.hotkey.ss58_address, + netuid=1, + children=fake_children, + wait_for_finalization=True, + wait_for_inclusion=True, + raise_error=False, + period=None, + ) + assert result == mocked_set_children_extrinsic.return_value From e226f145a310dc7dc44894907cf530508593c6ac Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Jun 2025 10:03:10 -0700 Subject: [PATCH 08/12] wait_for_finalization=False by default --- bittensor/core/extrinsics/asyncex/children.py | 111 +++++++++++------- bittensor/core/extrinsics/children.py | 27 ++++- 2 files changed, 89 insertions(+), 49 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 474fbb3f54..46853642fe 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -13,7 +13,7 @@ async def set_children_extrinsic( netuid: int, children: list[tuple[float, str]], wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, + wait_for_finalization: bool = False, raise_error: bool = False, period: Optional[int] = None, ): @@ -55,37 +55,46 @@ async def set_children_extrinsic( if not unlock.success: return False, unlock.message - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - float_to_u64(proportion), - child_hotkey, - ) - for proportion, child_hotkey in children - ], - "hotkey": hotkey, - "netuid": netuid, - }, - ) - - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - raise_error=raise_error, - period=period, - ) + async with subtensor.substrate as substrate: + call = await substrate.compose_call( + call_module="SubtensorModule", + call_function="set_children", + call_params={ + "children": [ + ( + float_to_u64(proportion), + child_hotkey, + ) + for proportion, child_hotkey in children + ], + "hotkey": hotkey, + "netuid": netuid, + }, + ) + + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + raise_error=raise_error, + period=period, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return True, message + + if success: + return True, "Success with `set_children_extrinsic` response." + + return True, message async def root_set_pending_childkey_cooldown_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", cooldown: int, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, ) -> tuple[bool, str]: @@ -97,22 +106,34 @@ async def root_set_pending_childkey_cooldown_extrinsic( if not unlock.success: return False, unlock.message - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_pending_childkey_cooldown", - call_params={"cooldown": cooldown}, - ) - - sudo_call = await subtensor.substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={"call": call}, - ) - - return await subtensor.sign_and_send_extrinsic( - call=sudo_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) + async with subtensor.substrate as substrate: + call = await substrate.compose_call( + call_module="SubtensorModule", + call_function="set_pending_childkey_cooldown", + call_params={"cooldown": cooldown}, + ) + + sudo_call = await substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={"call": call}, + ) + + success, message = await subtensor.sign_and_send_extrinsic( + call=sudo_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return True, message + + if success: + return ( + True, + "Success with `root_set_pending_childkey_cooldown_extrinsic` response.", + ) + + return True, message diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 3e3111e846..dd91fbe97b 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -13,7 +13,7 @@ def set_children_extrinsic( netuid: int, children: list[tuple[float, str]], wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, + wait_for_finalization: bool = False, raise_error: bool = False, period: Optional[int] = None, ): @@ -71,7 +71,7 @@ def set_children_extrinsic( }, ) - return subtensor.sign_and_send_extrinsic( + success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -80,12 +80,20 @@ def set_children_extrinsic( period=period, ) + if not wait_for_finalization and not wait_for_inclusion: + return True, message + + if success: + return True, "Success with `set_children_extrinsic` response." + + return True, message + def root_set_pending_childkey_cooldown_extrinsic( subtensor: "Subtensor", wallet: "Wallet", cooldown: int, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, ) -> tuple[bool, str]: @@ -109,10 +117,21 @@ def root_set_pending_childkey_cooldown_extrinsic( call_params={"call": call}, ) - return subtensor.sign_and_send_extrinsic( + success, message = subtensor.sign_and_send_extrinsic( call=sudo_call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, ) + + if not wait_for_finalization and not wait_for_inclusion: + return True, message + + if success: + return ( + True, + "Success with `root_set_pending_childkey_cooldown_extrinsic` response.", + ) + + return True, message From 54f7193505134d3da28ba9f83ab4b7a912aa81fe Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Jun 2025 10:03:30 -0700 Subject: [PATCH 09/12] add tests for extrinsics --- .../extrinsics/asyncex/test_children.py | 94 +++++++++++++++++++ tests/unit_tests/extrinsics/test_children.py | 86 +++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 tests/unit_tests/extrinsics/asyncex/test_children.py create mode 100644 tests/unit_tests/extrinsics/test_children.py diff --git a/tests/unit_tests/extrinsics/asyncex/test_children.py b/tests/unit_tests/extrinsics/asyncex/test_children.py new file mode 100644 index 0000000000..da107dfad4 --- /dev/null +++ b/tests/unit_tests/extrinsics/asyncex/test_children.py @@ -0,0 +1,94 @@ +import pytest + +from bittensor.core.extrinsics.asyncex import children + + +@pytest.mark.asyncio +async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): + """Test that set_children_extrinsic correctly constructs and submits the extrinsic.""" + # Preps + hotkey = "fake hotkey" + netuid = 123 + fake_children = [ + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ] + + substrate = subtensor.substrate.__aenter__.return_value + substrate.compose_call = mocker.AsyncMock() + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + success, message = await children.set_children_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + hotkey=hotkey, + netuid=netuid, + children=fake_children, + ) + + # Asserts + substrate.compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="set_children", + call_params={ + "children": [ + ( + 18446744073709551615, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ], + "hotkey": "fake hotkey", + "netuid": netuid, + }, + ) + + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=substrate.compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=False, + period=None, + raise_error=False, + ) + + assert success is True + assert "Success" in message + + +@pytest.mark.asyncio +async def test_root_set_pending_childkey_cooldown_extrinsic( + subtensor, mocker, fake_wallet +): + """Verify root_set_pending_childkey_cooldown_extrinsic extrinsic.""" + # Preps + cooldown = 100 + + substrate = subtensor.substrate.__aenter__.return_value + substrate.compose_call = mocker.AsyncMock() + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + success, message = await children.root_set_pending_childkey_cooldown_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + cooldown=cooldown, + ) + # Asserts + + substrate.compose_call.call_count == 2 + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=substrate.compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=False, + period=None, + ) + assert success is True + assert "Success" in message diff --git a/tests/unit_tests/extrinsics/test_children.py b/tests/unit_tests/extrinsics/test_children.py new file mode 100644 index 0000000000..2af3517fff --- /dev/null +++ b/tests/unit_tests/extrinsics/test_children.py @@ -0,0 +1,86 @@ +from bittensor.core.extrinsics import children + + +def test_set_children_extrinsic(subtensor, mocker, fake_wallet): + """Test that set_children_extrinsic correctly constructs and submits the extrinsic.""" + # Preps + hotkey = "fake hotkey" + netuid = 123 + fake_children = [ + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ] + + subtensor.substrate.compose_call = mocker.Mock() + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + success, message = children.set_children_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + hotkey=hotkey, + netuid=netuid, + children=fake_children, + ) + + # Asserts + subtensor.substrate.compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="set_children", + call_params={ + "children": [ + ( + 18446744073709551615, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ], + "hotkey": "fake hotkey", + "netuid": netuid, + }, + ) + + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=subtensor.substrate.compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=False, + period=None, + raise_error=False, + ) + + assert success is True + assert "Success" in message + + +def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wallet): + """Verify root_set_pending_childkey_cooldown_extrinsic extrinsic.""" + # Preps + cooldown = 100 + + subtensor.substrate.compose_call = mocker.Mock() + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + success, message = children.root_set_pending_childkey_cooldown_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + cooldown=cooldown, + ) + # Asserts + + subtensor.substrate.compose_call.call_count == 2 + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=subtensor.substrate.compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=False, + period=None, + ) + assert success is True + assert "Success" in message From 3673e1b015367108349ccc4e4b58fefd6f34e3c3 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Jun 2025 10:12:22 -0700 Subject: [PATCH 10/12] improve e2e test --- tests/e2e_tests/test_hotkeys.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 918335f26a..4d30383929 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -78,6 +78,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w """ Tests: - Get default children (empty list) + - Call `root_set_pending_childkey_cooldown` extrinsic. - Update children list - Checking pending children - Checking cooldown period @@ -93,7 +94,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w wallet=alice_wallet, cooldown=ROOT_COOLDOWN ) assert success, f"Call `root_set_pending_childkey_cooldown` failed: {message}" - assert message == "" + assert message == "Success with `root_set_pending_childkey_cooldown_extrinsic` response." assert subtensor.register_subnet(dave_wallet, True, True) assert subtensor.subnet_exists(dave_subnet_netuid), ( @@ -249,7 +250,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w raise_error=True, ) - assert error == "" + assert error == "Success with `set_children_extrinsic` response." assert success is True set_children_block = subtensor.get_current_block() From 69b41365e70f17cee6a586134ac4330f50d438d1 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Jun 2025 10:14:31 -0700 Subject: [PATCH 11/12] ruff --- tests/e2e_tests/test_hotkeys.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 4d30383929..5831e4bf27 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -94,7 +94,10 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w wallet=alice_wallet, cooldown=ROOT_COOLDOWN ) assert success, f"Call `root_set_pending_childkey_cooldown` failed: {message}" - assert message == "Success with `root_set_pending_childkey_cooldown_extrinsic` response." + assert ( + message + == "Success with `root_set_pending_childkey_cooldown_extrinsic` response." + ) assert subtensor.register_subnet(dave_wallet, True, True) assert subtensor.subnet_exists(dave_subnet_netuid), ( From e673c2314e310a58b2c8015cc9e7da1800e78f35 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 17 Jun 2025 10:26:08 -0700 Subject: [PATCH 12/12] opps, remove wrong import set_children_extrinsic --- bittensor/core/async_subtensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 53552c4b12..16a40a6608 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -82,7 +82,6 @@ set_weights_extrinsic, reveal_weights_extrinsic, ) -from bittensor.core.extrinsics.children import set_children_extrinsic from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import version_as_int, TYPE_REGISTRY from bittensor.core.types import ParamWithTypes, SubtensorMixin