From 5752085dadee2d3d3aa5127206b0ae620d5ba95a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 19:02:19 -0700 Subject: [PATCH 01/39] add `_sub*` extrinsics --- .../core/extrinsics/asyncex/sub_subnet.py | 380 ++++++++++++++++++ bittensor/core/extrinsics/sub_subnet.py | 380 ++++++++++++++++++ bittensor/utils/__init__.py | 39 +- 3 files changed, 795 insertions(+), 4 deletions(-) create mode 100644 bittensor/core/extrinsics/asyncex/sub_subnet.py create mode 100644 bittensor/core/extrinsics/sub_subnet.py diff --git a/bittensor/core/extrinsics/asyncex/sub_subnet.py b/bittensor/core/extrinsics/asyncex/sub_subnet.py new file mode 100644 index 0000000000..a10586d06f --- /dev/null +++ b/bittensor/core/extrinsics/asyncex/sub_subnet.py @@ -0,0 +1,380 @@ +from typing import TYPE_CHECKING, Optional, Union + +import numpy as np +from bittensor_drand import get_encrypted_commit +from numpy.typing import NDArray + +from bittensor.core.settings import version_as_int +from bittensor.utils import unlock_key, get_sub_subnet_storage_index, torch +from bittensor.utils.btlogging import logging +from bittensor.utils.weight_utils import ( + convert_and_normalize_weights_and_uids, + generate_weight_hash, +) + +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.core.async_subtensor import AsyncSubtensor + + +async def commit_sub_weights_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], list], + weights: Union[NDArray[np.int64], list], + salt: list[int], + version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Parameters: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + subuid: The sub-subnet unique identifier. + uids: NumPy array of neuron UIDs for which weights are being committed. + weights: NumPy array of weight values corresponding to each UID. + salt: list of randomly generated integers as salt to generated weighted hash. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + # Generate the hash of the weights + commit_hash = generate_weight_hash( + address=wallet.hotkey.ss58_address, + netuid=netuid, + subuid=subuid, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=version_key, + ) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit_hash": commit_hash, + }, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug(message) + return True, message + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) + + +async def commit_timelocked_sub_weights_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], + weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + block_time: Union[int, float], + commit_reveal_version: int = 4, + version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Parameters: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + subuid: The sub-subnet unique identifier. + uids: The list of neuron UIDs that the weights are being set for. + weights: The corresponding weights to be set for each UID. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the commit-reveal in the chain. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + current_block = await subtensor.block + subnet_hyperparameters = await subtensor.get_subnet_hyperparameters( + netuid, block=current_block + ) + tempo = subnet_hyperparameters.tempo + subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period + + storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) + + # Encrypt `commit_hash` with t-lock and `get reveal_round` + commit_for_reveal, reveal_round = get_encrypted_commit( + uids=uids, + weights=weights, + version_key=version_key, + tempo=tempo, + current_block=current_block, + netuid=storage_index, + subnet_reveal_period_epochs=subnet_reveal_period_epochs, + block_time=block_time, + hotkey=wallet.hotkey.public_key, + ) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_timelocked_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + }, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug(message) + return True, f"reveal_round:{reveal_round}" + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) + + +async def reveal_sub_weights_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], list[int]], + weights: Union[NDArray[np.float32], list[int]], + salt: list[int], + version_key: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Args: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + subuid: The sub-subnet unique identifier. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + salt: List of salt values corresponding to the hash function. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="reveal_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "uids": uids, + "values": weights, + "salt": salt, + "version_key": version_key, + }, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug(message) + return True, message + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) + + +async def set_sub_weights_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], + weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + version_key: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. + + Args: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + subuid: The sub-subnet unique identifier. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + # Convert, reformat and normalize. + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "dests": uids, + "weights": weights, + "version_key": version_key, + }, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug("Successfully set weights and Finalized.") + return True, message + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) diff --git a/bittensor/core/extrinsics/sub_subnet.py b/bittensor/core/extrinsics/sub_subnet.py new file mode 100644 index 0000000000..f6b0b316bb --- /dev/null +++ b/bittensor/core/extrinsics/sub_subnet.py @@ -0,0 +1,380 @@ +from typing import TYPE_CHECKING, Optional, Union + +import numpy as np +from bittensor_drand import get_encrypted_commit +from numpy.typing import NDArray + +from bittensor.core.settings import version_as_int +from bittensor.utils import unlock_key, get_sub_subnet_storage_index, torch +from bittensor.utils.btlogging import logging +from bittensor.utils.weight_utils import ( + convert_and_normalize_weights_and_uids, + generate_weight_hash, +) + +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.core.subtensor import Subtensor + + +def commit_sub_weights_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], list], + weights: Union[NDArray[np.int64], list], + salt: list[int], + version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + subuid: The sub-subnet unique identifier. + uids: NumPy array of neuron UIDs for which weights are being committed. + weights: NumPy array of weight values corresponding to each UID. + salt: list of randomly generated integers as salt to generated weighted hash. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + # Generate the hash of the weights + commit_hash = generate_weight_hash( + address=wallet.hotkey.ss58_address, + netuid=netuid, + subuid=subuid, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=version_key, + ) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit_hash": commit_hash, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug(message) + return True, message + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) + + +def commit_timelocked_sub_weights_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], + weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + block_time: Union[int, float], + commit_reveal_version: int = 4, + version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + subuid: The sub-subnet unique identifier. + uids: The list of neuron UIDs that the weights are being set for. + weights: The corresponding weights to be set for each UID. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the commit-reveal in the chain. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + current_block = subtensor.get_current_block() + subnet_hyperparameters = subtensor.get_subnet_hyperparameters( + netuid, block=current_block + ) + tempo = subnet_hyperparameters.tempo + subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period + + storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) + + # Encrypt `commit_hash` with t-lock and `get reveal_round` + commit_for_reveal, reveal_round = get_encrypted_commit( + uids=uids, + weights=weights, + version_key=version_key, + tempo=tempo, + current_block=current_block, + netuid=storage_index, + subnet_reveal_period_epochs=subnet_reveal_period_epochs, + block_time=block_time, + hotkey=wallet.hotkey.public_key, + ) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_timelocked_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug(message) + return True, f"reveal_round:{reveal_round}" + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) + + +def reveal_sub_weights_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], list[int]], + weights: Union[NDArray[np.float32], list[int]], + salt: list[int], + version_key: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + subuid: The sub-subnet unique identifier. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + salt: List of salt values corresponding to the hash function. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="reveal_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "uids": uids, + "values": weights, + "salt": salt, + "version_key": version_key, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + period=period, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug(message) + return True, message + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) + + +def set_sub_weights_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + subuid: int, + uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], + weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + version_key: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + subuid: The sub-subnet unique identifier. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + version_key: Version key for compatibility with the network. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + + # Convert, reformat and normalize. + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "dests": uids, + "weights": weights, + "version_key": version_key, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + raise_error=raise_error, + ) + + if success: + logging.debug("Successfully set weights and Finalized.") + return True, message + + logging.error(message) + return False, message + + except Exception as error: + if raise_error: + raise error + logging.error(str(error)) + + return False, str(error) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 57d528f76f..2e2c2edeb5 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -25,7 +25,12 @@ from bittensor.utils.balance import Balance BT_DOCS_LINK = "https://docs.bittensor.com" +RAOPERTAO = 1e9 +U16_MAX = 65535 +U64_MAX = 18446744073709551615 +GLOBAL_MAX_SUBNET_COUNT = 4096 +UnlockStatus = namedtuple("UnlockStatus", ["success", "message"]) # redundant aliases logging = logging @@ -38,11 +43,37 @@ hex_to_bytes = hex_to_bytes -RAOPERTAO = 1e9 -U16_MAX = 65535 -U64_MAX = 18446744073709551615 +def get_sub_subnet_storage_index(netuid: int, subuid: int) -> int: + """Computes the storage index for a given netuid and subuid pair. -UnlockStatus = namedtuple("UnlockStatus", ["success", "message"]) + Parameters: + netuid: The netuid of the subnet. + subuid: The subuid of the subnet. + + Returns: + Storage index number for the subnet and subuid. + """ + return subuid * GLOBAL_MAX_SUBNET_COUNT + netuid + + +def get_netuid_and_subuid_by_storage_index(storage_index: int) -> tuple[int, int]: + """Returns the netuid and subuid from the storage index. + + Chain APIs (e.g., SubMetagraph response) returns netuid which is storage index that encodes both the netuid and + subuid. This function reverses the encoding to extract these components. + + Parameters: + storage_index: The storage index of the subnet. + + Returns: + tuple[int, int]: + - netuid subnet identifier. + - subuid identifier. + """ + return ( + storage_index % GLOBAL_MAX_SUBNET_COUNT, + storage_index // GLOBAL_MAX_SUBNET_COUNT, + ) class Certificate(str): From 5fdc8e9d0128834844941df1b334413e78c19106 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 19:02:35 -0700 Subject: [PATCH 02/39] add sudo `_sub*` extrinsics --- bittensor/core/extrinsics/asyncex/sudo.py | 143 ++++++++++++++++++++++ bittensor/core/extrinsics/sudo.py | 143 ++++++++++++++++++++++ 2 files changed, 286 insertions(+) create mode 100644 bittensor/core/extrinsics/asyncex/sudo.py create mode 100644 bittensor/core/extrinsics/sudo.py diff --git a/bittensor/core/extrinsics/asyncex/sudo.py b/bittensor/core/extrinsics/asyncex/sudo.py new file mode 100644 index 0000000000..8823d02687 --- /dev/null +++ b/bittensor/core/extrinsics/asyncex/sudo.py @@ -0,0 +1,143 @@ +from typing import Optional, TYPE_CHECKING + +from bittensor.core.extrinsics.asyncex.utils import sudo_call_extrinsic + +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.core.async_subtensor import AsyncSubtensor + + +async def sudo_set_admin_freez_window( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + window: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the admin freeze window length (in blocks) at the end of a tempo. + + Parameters: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + window: The amount of blocks to freeze in the end of a tempo. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + call_function = "sudo_set_admin_freeze_window" + call_params = {"window": window} + return await sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_function=call_function, + call_params=call_params, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + +async def sudo_set_sub_subnet_count_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + sub_count: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the number of sub-subnets in the subnet. + + Parameters: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + sub_count: The amount of sub-subnets in the subnet to be set. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + call_function = "sudo_set_subsubnet_count" + call_params = {"netuid": netuid, "subsub_count": sub_count} + return await sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_function=call_function, + call_params=call_params, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + +async def sudo_set_sub_subnet_emission_split( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + maybe_split: list[int], + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the emission split between sub-subnets in a provided subnet. + + Parameters: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + maybe_split: List of emission weights (positive integers) for each sub-subnet. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + + Note: + The `maybe_split` list defines the relative emission share for each sub-subnet. + Its length must match the number of active sub-subnets in the subnet. For example, [3, 1, 1] distributes + emissions in a 3:1:1 ratio across sub-subnets 0, 1, and 2. Each sub-subnet's emission share is calculated as: + share[i] = maybe_split[i] / sum(maybe_split) + """ + call_function = "sudo_set_subsubnet_emission_split" + call_params = {"netuid": netuid, "maybe_split": maybe_split} + return await sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_function=call_function, + call_params=call_params, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) diff --git a/bittensor/core/extrinsics/sudo.py b/bittensor/core/extrinsics/sudo.py new file mode 100644 index 0000000000..d734985485 --- /dev/null +++ b/bittensor/core/extrinsics/sudo.py @@ -0,0 +1,143 @@ +from typing import Optional, TYPE_CHECKING + +from bittensor.core.extrinsics.utils import sudo_call_extrinsic + +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.core.subtensor import Subtensor + + +def sudo_set_admin_freez_window( + subtensor: "Subtensor", + wallet: "Wallet", + window: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the admin freeze window length (in blocks) at the end of a tempo. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + window: The amount of blocks to freeze in the end of a tempo. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + call_function = "sudo_set_admin_freeze_window" + call_params = {"window": window} + return sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_function=call_function, + call_params=call_params, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + +def sudo_set_sub_subnet_count_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + sub_count: int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the number of sub-subnets in the subnet. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + sub_count: The amount of sub-subnets in the subnet to be set. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + call_function = "sudo_set_subsubnet_count" + call_params = {"netuid": netuid, "subsub_count": sub_count} + return sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_function=call_function, + call_params=call_params, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + +def sudo_set_sub_subnet_emission_split( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + maybe_split: list[int], + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """ + Sets the emission split between sub-subnets in a provided subnet. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + maybe_split: List of emission weights (positive integers) for each sub-subnet. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + + Note: + The `maybe_split` list defines the relative emission share for each sub-subnet. + Its length must match the number of active sub-subnets in the subnet. For example, [3, 1, 1] distributes + emissions in a 3:1:1 ratio across sub-subnets 0, 1, and 2. Each sub-subnet's emission share is calculated as: + share[i] = maybe_split[i] / sum(maybe_split) + """ + call_function = "sudo_set_subsubnet_emission_split" + call_params = {"netuid": netuid, "maybe_split": maybe_split} + return sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_function=call_function, + call_params=call_params, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) From 249afc1677eacdf44afec5bea6a9b67158ba46d0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:13:38 -0700 Subject: [PATCH 03/39] add common `sudo_call_extrinsic` --- bittensor/core/extrinsics/asyncex/utils.py | 75 ++++++++++++++++++++++ bittensor/core/extrinsics/utils.py | 74 +++++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index 7c756e2499..a7322bba6e 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -1,11 +1,14 @@ from typing import TYPE_CHECKING, Optional +from bittensor.utils import unlock_key from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging if TYPE_CHECKING: from scalecodec import GenericCall from bittensor_wallet import Keypair from bittensor.core.async_subtensor import AsyncSubtensor + from bittensor_wallet import Wallet async def get_extrinsic_fee( @@ -32,3 +35,75 @@ async def get_extrinsic_fee( return Balance.from_rao(amount=payment_info["partial_fee"]).set_unit( netuid=netuid or 0 ) + + +async def sudo_call_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + call_function: str, + call_params: dict, + call_module: str = "AdminUtils", + sign_with: str = "coldkey", + use_nonce: bool = False, + nonce_key: str = "hotkey", + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """Execute a sudo call extrinsic. + + Parameters: + subtensor: AsyncSubtensor instance. + wallet: The wallet instance. + call_function: The call function to execute. + call_params: The call parameters. + call_module: The call module. + sign_with: The keypair to sign the extrinsic with. + use_nonce: Whether to use a nonce. + nonce_key: The key to use for the nonce. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + sudo_call = await subtensor.substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={ + "call": subtensor.substrate.compose_call( + call_module=call_module, + call_function=call_function, + call_params=call_params, + ) + }, + ) + return await subtensor.sign_and_send_extrinsic( + call=sudo_call, + wallet=wallet, + sign_with=sign_with, + use_nonce=use_nonce, + nonce_key=nonce_key, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + except Exception as error: + if raise_error: + raise error + + return False, str(error) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 5092791f66..397a57a3fb 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -2,7 +2,9 @@ from typing import TYPE_CHECKING, Optional +from bittensor.utils import unlock_key from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging if TYPE_CHECKING: from scalecodec import GenericCall @@ -68,3 +70,75 @@ def get_extrinsic_fee( return Balance.from_rao(amount=payment_info["partial_fee"]).set_unit( netuid=netuid or 0 ) + + +def sudo_call_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + call_function: str, + call_params: dict, + call_module: str = "AdminUtils", + sign_with: str = "coldkey", + use_nonce: bool = False, + nonce_key: str = "hotkey", + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: + """Execute a sudo call extrinsic. + + Parameters: + subtensor: The Subtensor instance. + wallet: The wallet instance. + call_function: The call function to execute. + call_params: The call parameters. + call_module: The call module. + sign_with: The keypair to sign the extrinsic with. + use_nonce: Whether to use a nonce. + nonce_key: The key to use for the nonce. + 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + """ + try: + unlock = unlock_key(wallet, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + return False, unlock.message + sudo_call = subtensor.substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={ + "call": subtensor.substrate.compose_call( + call_module=call_module, + call_function=call_function, + call_params=call_params, + ) + }, + ) + return subtensor.sign_and_send_extrinsic( + call=sudo_call, + wallet=wallet, + sign_with=sign_with, + use_nonce=use_nonce, + nonce_key=nonce_key, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + except Exception as error: + if raise_error: + raise error + + return False, str(error) From 1c888c88c75107dd70b3a279a12e84dc935bcb42 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:15:36 -0700 Subject: [PATCH 04/39] add custom types for uids, weights, and salt --- bittensor/core/extrinsics/asyncex/staking.py | 3 ++- .../core/extrinsics/asyncex/sub_subnet.py | 27 ++++++++++--------- .../core/extrinsics/asyncex/unstaking.py | 4 ++- bittensor/core/extrinsics/staking.py | 3 ++- bittensor/core/extrinsics/sub_subnet.py | 27 ++++++++++--------- bittensor/core/extrinsics/unstaking.py | 5 ++-- bittensor/core/types.py | 18 +++++++++---- 7 files changed, 51 insertions(+), 36 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 76ffe73285..dcd227868d 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -4,6 +4,7 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -219,7 +220,7 @@ async def add_stake_multiple_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, old_balance: Optional[Balance] = None, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, diff --git a/bittensor/core/extrinsics/asyncex/sub_subnet.py b/bittensor/core/extrinsics/asyncex/sub_subnet.py index a10586d06f..2bcf214cdf 100644 --- a/bittensor/core/extrinsics/asyncex/sub_subnet.py +++ b/bittensor/core/extrinsics/asyncex/sub_subnet.py @@ -1,11 +1,10 @@ from typing import TYPE_CHECKING, Optional, Union -import numpy as np from bittensor_drand import get_encrypted_commit -from numpy.typing import NDArray from bittensor.core.settings import version_as_int -from bittensor.utils import unlock_key, get_sub_subnet_storage_index, torch +from bittensor.core.types import Salt, UIDs, Weights +from bittensor.utils import unlock_key, get_sub_subnet_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -22,9 +21,9 @@ async def commit_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], list], - weights: Union[NDArray[np.int64], list], - salt: list[int], + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int = version_as_int, period: Optional[int] = None, raise_error: bool = False, @@ -112,8 +111,8 @@ async def commit_timelocked_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + uids: UIDs, + weights: Weights, block_time: Union[int, float], commit_reveal_version: int = 4, version_key: int = version_as_int, @@ -219,9 +218,9 @@ async def reveal_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], list[int]], - weights: Union[NDArray[np.float32], list[int]], - salt: list[int], + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int, period: Optional[int] = None, raise_error: bool = False, @@ -258,6 +257,8 @@ async def reveal_sub_weights_extrinsic( logging.error(unlock.message) return False, unlock.message + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="reveal_sub_weights", @@ -302,8 +303,8 @@ async def set_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + uids: UIDs, + weights: Weights, version_key: int, period: Optional[int] = None, raise_error: bool = False, diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index fcc0416dd5..3bbc40a146 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -2,8 +2,10 @@ from typing import Optional, TYPE_CHECKING from async_substrate_interface.errors import SubstrateRequestException + from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -275,7 +277,7 @@ async def unstake_multiple_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index fc8b69c48b..8397cafe92 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -3,6 +3,7 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -209,7 +210,7 @@ def add_stake_multiple_extrinsic( subtensor: "Subtensor", wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, diff --git a/bittensor/core/extrinsics/sub_subnet.py b/bittensor/core/extrinsics/sub_subnet.py index f6b0b316bb..607a323af7 100644 --- a/bittensor/core/extrinsics/sub_subnet.py +++ b/bittensor/core/extrinsics/sub_subnet.py @@ -1,11 +1,10 @@ from typing import TYPE_CHECKING, Optional, Union -import numpy as np from bittensor_drand import get_encrypted_commit -from numpy.typing import NDArray from bittensor.core.settings import version_as_int -from bittensor.utils import unlock_key, get_sub_subnet_storage_index, torch +from bittensor.core.types import Salt, UIDs, Weights +from bittensor.utils import unlock_key, get_sub_subnet_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -22,9 +21,9 @@ def commit_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], list], - weights: Union[NDArray[np.int64], list], - salt: list[int], + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int = version_as_int, period: Optional[int] = None, raise_error: bool = False, @@ -112,8 +111,8 @@ def commit_timelocked_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + uids: UIDs, + weights: Weights, block_time: Union[int, float], commit_reveal_version: int = 4, version_key: int = version_as_int, @@ -219,9 +218,9 @@ def reveal_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], list[int]], - weights: Union[NDArray[np.float32], list[int]], - salt: list[int], + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int, period: Optional[int] = None, raise_error: bool = False, @@ -258,6 +257,8 @@ def reveal_sub_weights_extrinsic( logging.error(unlock.message) return False, unlock.message + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + call = subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="reveal_sub_weights", @@ -302,8 +303,8 @@ def set_sub_weights_extrinsic( wallet: "Wallet", netuid: int, subuid: int, - uids: Union[NDArray[np.int64], "torch.LongTensor", list[Union[int, float]]], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list[Union[int, float]]], + uids: UIDs, + weights: Weights, version_key: int, period: Optional[int] = None, raise_error: bool = False, diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 8f0ce3329d..bc546e36d8 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -1,9 +1,10 @@ from typing import Optional, TYPE_CHECKING from async_substrate_interface.errors import SubstrateRequestException -from bittensor.core.extrinsics.utils import get_extrinsic_fee +from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -274,7 +275,7 @@ def unstake_multiple_extrinsic( subtensor: "Subtensor", wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, diff --git a/bittensor/core/types.py b/bittensor/core/types.py index d0c8b79cc9..eb1e84478f 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,13 +1,21 @@ -from abc import ABC import argparse -from typing import TypedDict, Optional +from abc import ABC +from typing import TypedDict, Optional, Union + +import numpy as np +from numpy.typing import NDArray -from bittensor.utils import networking, Certificate -from bittensor.utils.btlogging import logging from bittensor.core import settings -from bittensor.core.config import Config from bittensor.core.chain_data import NeuronInfo, NeuronInfoLite +from bittensor.core.config import Config from bittensor.utils import determine_chain_endpoint_and_network +from bittensor.utils import networking, Certificate +from bittensor.utils.btlogging import logging + +# Type annotations for UIDs and weights. +UIDs = Union[NDArray[np.int64], list[Union[int]]] +Weights = Union[NDArray[np.float32], list[Union[int, float]]] +Salt = Union[NDArray[np.int64], list[int]] class SubtensorMixin(ABC): From cb5c4771112e5619969dc1a9dc0fcab26a856f89 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:16:44 -0700 Subject: [PATCH 05/39] add sub methods to subtensors --- bittensor/core/async_subtensor.py | 350 ++++++++++++++++++++++-------- bittensor/core/subtensor.py | 332 ++++++++++++++++++++-------- 2 files changed, 501 insertions(+), 181 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 67591b6c30..2ac30b0e7c 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -6,14 +6,12 @@ from typing import cast, Optional, Any, Union, Iterable, TYPE_CHECKING import asyncstdlib as a -import numpy as np import scalecodec from async_substrate_interface import AsyncSubstrateInterface from async_substrate_interface.substrate_addons import RetryAsyncSubstrate from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT -from numpy.typing import NDArray from scalecodec import GenericCall from bittensor.core.chain_data import ( @@ -45,7 +43,12 @@ root_set_pending_childkey_cooldown_extrinsic, set_children_extrinsic, ) -from bittensor.core.extrinsics.asyncex.commit_reveal import commit_reveal_v3_extrinsic +from bittensor.core.extrinsics.asyncex.liquidity import ( + add_liquidity_extrinsic, + modify_liquidity_extrinsic, + remove_liquidity_extrinsic, + toggle_user_liquidity_extrinsic, +) from bittensor.core.extrinsics.asyncex.move_stake import ( transfer_stake_extrinsic, swap_stake_extrinsic, @@ -72,6 +75,12 @@ add_stake_multiple_extrinsic, ) from bittensor.core.extrinsics.asyncex.start_call import start_call_extrinsic +from bittensor.core.extrinsics.asyncex.sub_subnet import ( + commit_sub_weights_extrinsic, + commit_timelocked_sub_weights_extrinsic, + reveal_sub_weights_extrinsic, + set_sub_weights_extrinsic, +) from bittensor.core.extrinsics.asyncex.take import ( decrease_take_extrinsic, increase_take_extrinsic, @@ -82,36 +91,30 @@ unstake_extrinsic, unstake_multiple_extrinsic, ) -from bittensor.core.extrinsics.asyncex.weights import ( - commit_weights_extrinsic, - set_weights_extrinsic, - reveal_weights_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.core.types import ( + ParamWithTypes, + Salt, + SubtensorMixin, + UIDs, + Weights, +) from bittensor.utils import ( Certificate, decode_hex_identity_dict, format_error_message, is_valid_ss58_address, - torch, u16_normalized_float, u64_normalized_float, get_transfer_fn_params, ) -from bittensor.core.extrinsics.asyncex.liquidity import ( - add_liquidity_extrinsic, - modify_liquidity_extrinsic, - remove_liquidity_extrinsic, - toggle_user_liquidity_extrinsic, -) +from bittensor.utils import deprecated_message from bittensor.utils.balance import ( Balance, fixed_to_float, check_and_convert_to_balance, ) -from bittensor.utils import deprecated_message from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import ( calculate_fees, @@ -121,7 +124,6 @@ LiquidityPosition, ) from bittensor.utils.weight_utils import ( - generate_weight_hash, convert_uids_and_weights, U16_MAX, ) @@ -2664,6 +2666,127 @@ async def get_stake_add_fee( amount=amount, netuid=netuid, block=block ) + async def get_sub_all_metagraphs( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[list["MetagraphInfo"]]: + """ + Retrieves all sub metagraphs for all sub-subnets. + + Parameters: + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + The list of metagraphs for all subnets with all sub-subnets if found, `None` otherwise. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_all_submetagraphs", + block_hash=block_hash, + ) + if query.value is None: + return None + + return MetagraphInfo.from_dict(query.value) + + async def get_sub_metagraph_info( + self, + netuid: int, + subuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional["MetagraphInfo"]: + """ + Retrieves metagraph information for the specified sub-subnet (netuid, subuid). + + Arguments: + netuid: Subnet identifier. + subuid: Sub-subnet identifier. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the given + netuid does not exist. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_submetagraph", + params=[netuid, subuid], + block_hash=block_hash, + ) + if query.value is None: + logging.error(f"Sub-subnet #{netuid}.{subuid} does not exist.") + return None + + return MetagraphInfo.from_dict(query.value) + + async def get_sub_selective_metagraph( + self, + netuid: int, + subuid: int, + field_indices: Union[list[SelectiveMetagraphIndex], list[int]], + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional["MetagraphInfo"]: + """ + Retrieves selective metagraph information for the specified sub-subnet (netuid, subuid). + + Arguments: + netuid: Subnet identifier. + subuid: Sub-subnet identifier. + field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the does not + exist. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + indexes = [ + f.value if isinstance(f, SelectiveMetagraphIndex) else f + for f in field_indices + ] + query = await self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_selective_submetagraph", + params=[netuid, subuid, indexes if 0 in indexes else [0] + indexes], + block_hash=block_hash, + ) + if query.value is None: + logging.error(f"Subnet #{netuid}.{subuid} does not exist.") + return None + + return MetagraphInfo.from_dict(query.value) + + async def get_sub_subnet_count( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> int: + """Returns number of subnets""" + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.query( + module="SubtensorModule", + storage_function="SubsubnetCountCurrent", + params=[netuid], + block_hash=block_hash, + ) + return query.value if query is not None and hasattr(query, "value") else 1 + async def get_subnet_info( self, netuid: int, @@ -2877,7 +3000,7 @@ async def get_stake_for_coldkey_and_hotkey( self, coldkey_ss58: str, hotkey_ss58: str, - netuids: Optional[list[int]] = None, + netuids: Optional[UIDs] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, @@ -2953,7 +3076,7 @@ async def get_stake_for_coldkey( if result is None: return [] - stakes = StakeInfo.list_from_dicts(result) # type: ignore + stakes: list[StakeInfo] = StakeInfo.list_from_dicts(result) return [stake for stake in stakes if stake.stake > 0] get_stake_info_for_coldkey = get_stake_for_coldkey @@ -3409,6 +3532,53 @@ async def immunity_period( ) return None if call is None else int(call) + async def is_in_admin_freeze_window( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> bool: + """ + Returns True if the current block is within the terminal freeze window of the tempo + for the given subnet. During this window, admin ops are prohibited to avoid interference + with validator weight submissions. + + Args: + netuid (int): The unique identifier of the subnet. + block (Optional[int]): The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. + + Returns: + bool: True if in freeze window, else False. + """ + tempo, next_epoch_start_block, window = await asyncio.gather( + self.tempo( + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ), + self.get_next_epoch_start_block( + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ), + self.get_admin_freeze_window( + block=block, block_hash=block_hash, reuse_block=reuse_block + ), + ) + # SN0 doesn't have admin_freeze_window + if tempo == 0: + return False + + if next_epoch_start_block is not None: + remaining = next_epoch_start_block - await self.block + return remaining < window + return False + async def is_fast_blocks(self): """Returns True if the node is running with fast blocks. False if not.""" return ( @@ -4447,7 +4617,7 @@ async def add_stake_multiple( self, wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -4528,14 +4698,15 @@ async def commit_weights( self, wallet: "Wallet", netuid: int, - salt: list[int], - uids: Union[NDArray[np.int64], list], - weights: Union[NDArray[np.int64], list], + salt: Salt, + uids: UIDs, + weights: Weights, version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, + subuid: int = 0, ) -> tuple[bool, str]: """ Commits a hash of the subnet validator's weight vector to the Bittensor blockchain using the provided wallet. @@ -4555,6 +4726,7 @@ async def commit_weights( 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. + subuid: The sub-subnet unique identifier. Returns: tuple[bool, str]: @@ -4577,23 +4749,16 @@ async def commit_weights( f"version_key=[blue]{version_key}[/blue]" ) - # Generate the hash of the weights - commit_hash = generate_weight_hash( - address=wallet.hotkey.ss58_address, - netuid=netuid, - uids=list(uids), - values=list(weights), - salt=salt, - version_key=version_key, - ) - while retries < max_retries and success is False: try: - success, message = await commit_weights_extrinsic( + success, message = await commit_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - commit_hash=commit_hash, + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, @@ -4869,41 +5034,42 @@ async def reveal_weights( self, wallet: "Wallet", netuid: int, - uids: Union[NDArray[np.int64], list], - weights: Union[NDArray[np.int64], list], - salt: Union[NDArray[np.int64], list], + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = None, + subuid: int = 0, ) -> tuple[bool, str]: """ - Reveals the weight vector for a specific subnet on the Bittensor blockchain using the provided wallet. - This action serves as a revelation of the subnet validator's previously committed weight distribution as part - of the commit-reveal mechanism. + Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. + This action serves as a revelation of the neuron's previously committed weight distribution. - Arguments: - wallet: The wallet associated with the subnet validator revealing the weights. - netuid: unique identifier of the subnet. - uids: NumPy array of subnet miner neuron UIDs for which weights are being revealed. + Parameters: + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + uids: NumPy array of neuron UIDs for which weights are being revealed. weights: NumPy array of weight values corresponding to each UID. - salt: NumPy array of salt values - version_key: Version key for compatibility with the network. Default is `int representation of - the Bittensor version`. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is - `False`. - max_retries: The number of maximum attempts to reveal weights. Default is `5`. - 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. + salt: NumPy array of salt values corresponding to the hash function. + version_key: Version key for compatibility with the network. + 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. + max_retries: The number of maximum attempts to reveal weights. + 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. + subuid: The sub-subnet unique identifier. Returns: - tuple[bool, str]: `True` if the weight revelation is successful, False otherwise. And `msg`, a string - value describing the success or potential error. + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. - This function allows subnet validators to reveal their previously committed weight vector. + This function allows neurons to reveal their previously committed weight distribution, ensuring transparency and + accountability within the Bittensor network. See also: , """ @@ -4913,13 +5079,14 @@ async def reveal_weights( while retries < max_retries and success is False: try: - success, message = await reveal_weights_extrinsic( + success, message = await reveal_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - uids=list(uids), - weights=list(weights), - salt=list(salt), + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, version_key=version_key, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, @@ -5005,8 +5172,8 @@ async def root_register( async def root_set_weights( self, wallet: "Wallet", - netuids: list[int], - weights: list[float], + netuids: UIDs, + weights: Weights, version_key: int = 0, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, @@ -5232,14 +5399,16 @@ async def set_weights( self, wallet: "Wallet", netuid: int, - uids: Union[NDArray[np.int64], "torch.LongTensor", list], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + uids: UIDs, + weights: Weights, version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, max_retries: int = 5, block_time: float = 12.0, period: Optional[int] = 8, + subuid: int = 0, + commit_reveal_version: int = 4, ): """ Sets the weight vector for a neuron acting as a validator, specifying the weights assigned to subnet miners @@ -5249,26 +5418,26 @@ async def set_weights( work. These weight vectors are used by the Yuma Consensus algorithm to compute emissions for both validators and miners. - Arguments: - wallet: The wallet associated with the subnet validator setting the weights. + Parameters: + wallet: The wallet associated with the neuron setting the weights. netuid: The unique identifier of the subnet. - uids: The list of subnet miner neuron UIDs that the weights are being set for. - weights: The corresponding weights to be set for each UID, representing the validator's evaluation of each - miner's performance. - version_key: Version key for compatibility with the network. Default is int representation of - the Bittensor version. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is - `False`. - max_retries: The number of maximum attempts to set weights. Default is `5`. - block_time: The number of seconds for block duration. Default is 12.0 seconds. - 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. Default is 8. + uids: The list of neuron UIDs that the weights are being set for. + weights: The corresponding weights to be set for each UID. + version_key: Version key for compatibility with the network. + 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. + max_retries: The number of maximum attempts to set weights. + block_time: The number of seconds for block duration. + 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. + subuid: The sub-subnet unique identifier. + commit_reveal_version: The version of the commit-reveal in the chain. Returns: - tuple[bool, str]: `True` if the setting of weights is successful, False otherwise. And `msg`, a string - value describing the success or potential error. + tuple: + `True` if the setting of weights is successful, `False` otherwise. + `msg` is a string value describing the success or potential error. This function is crucial in the Yuma Consensus mechanism, where each validator's weight vector contributes to the overall weight matrix used to calculate emissions and maintain network consensus. @@ -5298,7 +5467,7 @@ async def _blocks_weight_limit() -> bool: ) if await self.commit_reveal_enabled(netuid=netuid): - # go with `commit reveal v3` extrinsic + # go with `commit_timelocked_sub_weights_extrinsic` extrinsic while ( retries < max_retries @@ -5308,10 +5477,11 @@ async def _blocks_weight_limit() -> bool: logging.info( f"Committing weights for subnet #{netuid}. Attempt {retries + 1} of {max_retries}." ) - success, message = await commit_reveal_v3_extrinsic( + success, message = await commit_timelocked_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, + subuid=subuid, uids=uids, weights=weights, version_key=version_key, @@ -5319,11 +5489,12 @@ async def _blocks_weight_limit() -> bool: wait_for_finalization=wait_for_finalization, block_time=block_time, period=period, + commit_reveal_version=commit_reveal_version, ) retries += 1 return success, message else: - # go with classic `set weights extrinsic` + # go with `set_sub_weights_extrinsic` while ( retries < max_retries @@ -5335,10 +5506,11 @@ async def _blocks_weight_limit() -> bool: f"Setting weights for subnet #[blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - success, message = await set_weights_extrinsic( + success, message = await set_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, + subuid=subuid, uids=uids, weights=weights, version_key=version_key, @@ -5763,7 +5935,7 @@ async def unstake_multiple( self, wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 257ede8b60..a201ee28b2 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3,7 +3,6 @@ from functools import lru_cache from typing import TYPE_CHECKING, Any, Iterable, Optional, Union, cast -import numpy as np import scalecodec from async_substrate_interface.errors import SubstrateRequestException from async_substrate_interface.substrate_addons import RetrySyncSubstrate @@ -11,7 +10,6 @@ from async_substrate_interface.types import ScaleObj from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment -from numpy.typing import NDArray from bittensor.core.async_subtensor import ProposalVoteData from bittensor.core.axon import Axon @@ -43,11 +41,6 @@ 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, - reveal_weights_extrinsic, -) from bittensor.core.extrinsics.liquidity import ( add_liquidity_extrinsic, modify_liquidity_extrinsic, @@ -75,12 +68,17 @@ get_metadata, serve_axon_extrinsic, ) -from bittensor.core.extrinsics.set_weights import set_weights_extrinsic from bittensor.core.extrinsics.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, ) from bittensor.core.extrinsics.start_call import start_call_extrinsic +from bittensor.core.extrinsics.sub_subnet import ( + commit_sub_weights_extrinsic, + commit_timelocked_sub_weights_extrinsic, + reveal_sub_weights_extrinsic, + set_sub_weights_extrinsic, +) from bittensor.core.extrinsics.take import ( decrease_take_extrinsic, increase_take_extrinsic, @@ -97,13 +95,18 @@ SS58_FORMAT, TYPE_REGISTRY, ) -from bittensor.core.types import ParamWithTypes, SubtensorMixin +from bittensor.core.types import ( + ParamWithTypes, + Salt, + SubtensorMixin, + UIDs, + Weights, +) from bittensor.utils import ( Certificate, decode_hex_identity_dict, format_error_message, is_valid_ss58_address, - torch, u16_normalized_float, u64_normalized_float, deprecated_message, @@ -124,7 +127,6 @@ LiquidityPosition, ) from bittensor.utils.weight_utils import ( - generate_weight_hash, convert_uids_and_weights, U16_MAX, ) @@ -1871,6 +1873,114 @@ def get_stake_add_fee( """ return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) + def get_sub_all_metagraphs( + self, + block: Optional[int] = None, + ) -> Optional[list["MetagraphInfo"]]: + """ + Retrieves all sub metagraphs for all sub-subnets. + + Parameters: + block: The blockchain block number for the query. + + Returns: + The list of metagraphs for all subnets with all sub-subnets if found, `None` otherwise. + """ + block_hash = self.determine_block_hash(block) + query = self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_all_submetagraphs", + block_hash=block_hash, + ) + if query.value is None: + return None + + return MetagraphInfo.from_dict(query.value) + + def get_sub_metagraph_info( + self, + netuid: int, + subuid: int, + block: Optional[int] = None, + ) -> Optional["MetagraphInfo"]: + """ + Retrieves metagraph information for the specified sub-subnet (netuid, subuid). + + Arguments: + netuid: Subnet identifier. + subuid: Sub-subnet identifier. + block: The blockchain block number for the query. + + Returns: + A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the given + netuid does not exist. + """ + block_hash = self.determine_block_hash(block) + + query = self.substrate.runtime_call( + "SubnetInfoRuntimeApi", + "get_submetagraph", + params=[netuid, subuid], + block_hash=block_hash, + ) + if query.value is None: + logging.error(f"Sub-subnet #{netuid}.{subuid} does not exist.") + return None + + return MetagraphInfo.from_dict(query.value) + + def get_sub_selective_metagraph( + self, + netuid: int, + subuid: int, + field_indices: Union[list[SelectiveMetagraphIndex], list[int]], + block: Optional[int] = None, + ) -> Optional["MetagraphInfo"]: + """ + Retrieves selective metagraph information for the specified sub-subnet (netuid, subuid). + + Arguments: + netuid: Subnet identifier. + subuid: Sub-subnet identifier. + field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + block: The blockchain block number for the query. + + Returns: + A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the does not + exist. + """ + block_hash = self.determine_block_hash(block=block) + indexes = [ + f.value if isinstance(f, SelectiveMetagraphIndex) else f + for f in field_indices + ] + query = self.substrate.runtime_call( + "SubnetInfoRuntimeApi", + "get_selective_submetagraph", + params=[netuid, subuid, indexes if 0 in indexes else [0] + indexes], + block_hash=block_hash, + ) + if query.value is None: + logging.error(f"Subnet #{netuid}.{subuid} does not exist.") + return None + + return MetagraphInfo.from_dict(query.value) + + def get_sub_subnet_count( + self, + netuid: int, + block: Optional[int] = None, + ) -> int: + """Returns number of subnets""" + block_hash = self.determine_block_hash(block) + query = self.substrate.query( + module="SubtensorModule", + storage_function="SubsubnetCountCurrent", + params=[netuid], + block_hash=block_hash, + ) + return query.value if query is not None and hasattr(query, "value") else 1 + def get_subnet_info( self, netuid: int, block: Optional[int] = None ) -> Optional["SubnetInfo"]: @@ -2051,7 +2161,7 @@ def get_stake_for_coldkey_and_hotkey( self, coldkey_ss58: str, hotkey_ss58: str, - netuids: Optional[list[int]] = None, + netuids: Optional[UIDs] = None, block: Optional[int] = None, ) -> dict[int, StakeInfo]: """ @@ -2100,13 +2210,13 @@ def get_stake_for_coldkey( result = self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", method="get_stake_info_for_coldkey", - params=[coldkey_ss58], # type: ignore + params=[coldkey_ss58], block=block, ) if result is None: return [] - stakes = StakeInfo.list_from_dicts(result) # type: ignore + stakes: list[StakeInfo] = StakeInfo.list_from_dicts(result) return [stake for stake in stakes if stake.stake > 0] get_stake_info_for_coldkey = get_stake_for_coldkey @@ -2244,7 +2354,7 @@ def get_subnet_reveal_period_epochs( ), ) - def get_subnets(self, block: Optional[int] = None) -> list[int]: + def get_subnets(self, block: Optional[int] = None) -> UIDs: """ Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. @@ -2467,6 +2577,38 @@ def immunity_period( ) return None if call is None else int(call) + def is_in_admin_freeze_window( + self, + netuid: int, + block: Optional[int] = None, + ) -> bool: + """ + Returns True if the current block is within the terminal freeze window of the tempo + for the given subnet. During this window, admin ops are prohibited to avoid interference + with validator weight submissions. + + Args: + netuid (int): The unique identifier of the subnet. + block (Optional[int]): The blockchain block number for the query. + + Returns: + bool: True if in freeze window, else False. + """ + tempo = self.tempo(netuid, block=block) + # SN0 doesn't have admin_freeze_window + if tempo == 0: + return False + + next_epoch_start_block = self.get_next_epoch_start_block( + netuid=netuid, block=block + ) + + if next_epoch_start_block is not None: + remaining = next_epoch_start_block - self.block + window = self.get_admin_freeze_window(block=block) + return remaining < window + return False + def is_fast_blocks(self): """Returns True if the node is running with fast blocks. False if not.""" return self.query_constant("SubtensorModule", "DurationOfStartCall").value == 10 @@ -3268,7 +3410,7 @@ def add_stake_multiple( self, wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -3355,34 +3497,34 @@ def commit_weights( self, wallet: "Wallet", netuid: int, - salt: list[int], - uids: Union[NDArray[np.int64], list], - weights: Union[NDArray[np.int64], list], + salt: Salt, + uids: UIDs, + weights: Weights, version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, + subuid: int = 0, ) -> tuple[bool, str]: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. This action serves as a commitment or snapshot of the neuron's current weight distribution. - Arguments: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. - netuid (int): The unique identifier of the subnet. - salt (list[int]): list of randomly generated integers as salt to generated weighted hash. - uids (np.ndarray): NumPy array of neuron UIDs for which weights are being committed. - weights (np.ndarray): NumPy array of weight values corresponding to each UID. - version_key (int): Version key for compatibility with the network. Default is ``int representation of - a Bittensor version.``. - 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``. - max_retries (int): The number of maximum attempts to commit weights. Default is ``5``. - 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. + Parameters: + wallet: The wallet associated with the neuron committing the weights. + netuid: The unique identifier of the subnet. + salt: list of randomly generated integers as salt to generated weighted hash. + uids: Array/list of neuron UIDs for which weights are being committed. + weights: Array/list of weight values corresponding to each UID. + version_key: Version key for compatibility with the network. + 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. + max_retries: The number of maximum attempts to commit weights. + 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. + subuid: The sub-subnet unique identifier. Returns: tuple[bool, str]: @@ -3402,23 +3544,16 @@ def commit_weights( f"version_key=[blue]{version_key}[/blue]" ) - # Generate the hash of the weights - commit_hash = generate_weight_hash( - address=wallet.hotkey.ss58_address, - netuid=netuid, - uids=list(uids), - values=list(weights), - salt=salt, - version_key=version_key, - ) - while retries < max_retries and success is False: try: - success, message = commit_weights_extrinsic( + success, message = commit_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - commit_hash=commit_hash, + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, @@ -3695,41 +3830,44 @@ def reveal_weights( self, wallet: "Wallet", netuid: int, - uids: Union[NDArray[np.int64], list], - weights: Union[NDArray[np.int64], list], - salt: Union[NDArray[np.int64], list], + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, + subuid: int = 0, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. This action serves as a revelation of the neuron's previously committed weight distribution. - Args: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. - netuid (int): The unique identifier of the subnet. - uids (np.ndarray): NumPy array of neuron UIDs for which weights are being revealed. - weights (np.ndarray): NumPy array of weight values corresponding to each UID. - salt (np.ndarray): NumPy array of salt values corresponding to the hash function. - version_key (int): Version key for compatibility with the network. Default is ``int representation of - the Bittensor version``. - 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``. - max_retries (int): The number of maximum attempts to reveal weights. Default is ``5``. - 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. + Parameters: + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + uids: NumPy array of neuron UIDs for which weights are being revealed. + weights: NumPy array of weight values corresponding to each UID. + salt: NumPy array of salt values corresponding to the hash function. + version_key: Version key for compatibility with the network. + 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. + max_retries: The number of maximum attempts to reveal weights. + 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. + subuid: The sub-subnet unique identifier. Returns: - tuple[bool, str]: ``True`` if the weight revelation is successful, False otherwise. And `msg`, a string - value describing the success or potential error. + tuple[bool, str]: + `True` if the extrinsic executed successfully, `False` otherwise. + `message` is a string value describing the success or potential error. + + This function allows neurons to reveal their previously committed weight distribution, ensuring transparency and + accountability within the Bittensor network. - This function allows neurons to reveal their previously committed weight distribution, ensuring transparency - and accountability within the Bittensor network. + See also: , """ retries = 0 success = False @@ -3737,13 +3875,14 @@ def reveal_weights( while retries < max_retries and success is False: try: - success, message = reveal_weights_extrinsic( + success, message = reveal_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - uids=list(uids), - weights=list(weights), - salt=list(salt), + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, version_key=version_key, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, @@ -3826,7 +3965,7 @@ def root_set_pending_childkey_cooldown( def root_set_weights( self, wallet: "Wallet", - netuids: list[int], + netuids: UIDs, weights: list[float], version_key: int = 0, wait_for_inclusion: bool = False, @@ -4041,42 +4180,48 @@ def set_weights( self, wallet: "Wallet", netuid: int, - uids: Union[NDArray[np.int64], "torch.LongTensor", list], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + uids: UIDs, + weights: Weights, version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, max_retries: int = 5, block_time: float = 12.0, period: Optional[int] = 8, + subuid: int = 0, + commit_reveal_version: int = 4, ) -> tuple[bool, str]: """ Sets the interneuronal weights for the specified neuron. This process involves specifying the influence or trust a neuron places on other neurons in the network, which is a fundamental aspect of Bittensor's decentralized learning architecture. - Arguments: + Parameters: wallet: The wallet associated with the neuron setting the weights. netuid: The unique identifier of the subnet. uids: The list of neuron UIDs that the weights are being set for. weights: The corresponding weights to be set for each UID. - version_key: Version key for compatibility with the network. Default is int representation of a Bittensor - version. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is ``False``. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is ``False``. - max_retries: The number of maximum attempts to set weights. Default is ``5``. - block_time: The number of seconds for block duration. Default is 12.0 seconds. - 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. Default is 8. + version_key: Version key for compatibility with the network. + 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. + max_retries: The number of maximum attempts to set weights. + block_time: The number of seconds for block duration. + 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. + subuid: The sub-subnet unique identifier. + commit_reveal_version: The version of the commit-reveal in the chain. Returns: tuple: `True` if the setting of weights is successful, `False` otherwise. `msg` is a string value describing the success or potential error. - This function is crucial in shaping the network's collective intelligence, where each neuron's learning and - contribution are influenced by the weights it sets towards others. + This function is crucial in the Yuma Consensus mechanism, where each validator's weight vector contributes to + the overall weight matrix used to calculate emissions and maintain network consensus. + + Notes: + See """ def _blocks_weight_limit() -> bool: @@ -4096,17 +4241,18 @@ def _blocks_weight_limit() -> bool: ) if self.commit_reveal_enabled(netuid=netuid): - # go with `commit reveal v3` extrinsic + # go with `commit_timelocked_sub_weights_extrinsic` extrinsic while retries < max_retries and success is False and _blocks_weight_limit(): logging.info( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - success, message = commit_reveal_v3_extrinsic( + success, message = commit_timelocked_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, + subuid=subuid, uids=uids, weights=weights, version_key=version_key, @@ -4114,11 +4260,12 @@ def _blocks_weight_limit() -> bool: wait_for_finalization=wait_for_finalization, block_time=block_time, period=period, + commit_reveal_version=commit_reveal_version, ) retries += 1 return success, message else: - # go with classic `set_weights_extrinsic` + # go with `set_sub_weights_extrinsic` while retries < max_retries and success is False and _blocks_weight_limit(): try: @@ -4126,10 +4273,11 @@ def _blocks_weight_limit() -> bool: f"Setting weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - success, message = set_weights_extrinsic( + success, message = set_sub_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, + subuid=subuid, uids=uids, weights=weights, version_key=version_key, @@ -4560,7 +4708,7 @@ def unstake_multiple( self, wallet: "Wallet", hotkey_ss58s: list[str], - netuids: list[int], + netuids: UIDs, amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, From a83c462f0c8f70723030de0f34f7829156f27915 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:17:16 -0700 Subject: [PATCH 06/39] TODO deprecate in SDKv10 --- bittensor/core/extrinsics/commit_reveal.py | 2 +- bittensor/core/extrinsics/commit_weights.py | 2 ++ bittensor/core/extrinsics/set_weights.py | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index 7709619d7e..44c6f16675 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -73,7 +73,7 @@ def _do_commit_reveal_v3( ) -# TODO: rename this extrinsic to `commit_reveal_extrinsic` in SDK.v10 +# TODO: deprecate in SDKv10 def commit_reveal_v3_extrinsic( subtensor: "Subtensor", wallet: "Wallet", diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index 511d1364ef..932a0c95d6 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -61,6 +61,7 @@ def _do_commit_weights( ) +# TODO: deprecate in SDKv10 def commit_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -175,6 +176,7 @@ def _do_reveal_weights( ) +# TODO: deprecate in SDKv10 def reveal_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", diff --git a/bittensor/core/extrinsics/set_weights.py b/bittensor/core/extrinsics/set_weights.py index 039dbdf837..b860620886 100644 --- a/bittensor/core/extrinsics/set_weights.py +++ b/bittensor/core/extrinsics/set_weights.py @@ -77,6 +77,7 @@ def _do_set_weights( return success, message +# TODO: deprecate in SDKv10 def set_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", From 8328991e98f97961b4dbe63602eb6454788a6681 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:17:32 -0700 Subject: [PATCH 07/39] update MetagraphInfo --- bittensor/core/chain_data/metagraph_info.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index cc329a84ad..28dce9336e 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -1,6 +1,5 @@ -from enum import Enum - from dataclasses import dataclass +from enum import Enum from typing import Optional, Union from bittensor.core import settings @@ -9,7 +8,11 @@ from bittensor.core.chain_data.info_base import InfoBase from bittensor.core.chain_data.subnet_identity import SubnetIdentity from bittensor.core.chain_data.utils import decode_account_id -from bittensor.utils import u64_normalized_float as u64tf, u16_normalized_float as u16tf +from bittensor.utils import ( + get_netuid_and_subuid_by_storage_index, + u64_normalized_float as u64tf, + u16_normalized_float as u16tf, +) from bittensor.utils.balance import Balance, fixed_to_float @@ -148,12 +151,13 @@ class MetagraphInfo(InfoBase): # List of validators validators: list[str] + subuid: int = 0 @classmethod def _from_dict(cls, decoded: dict) -> "MetagraphInfo": """Returns a MetagraphInfo object from decoded chain data.""" # Subnet index - _netuid = decoded["netuid"] + _netuid, _subuid = get_netuid_and_subuid_by_storage_index(decoded["netuid"]) # Name and symbol if name := decoded.get("name"): @@ -177,6 +181,7 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": return cls( # Subnet index netuid=_netuid, + subuid=_subuid, # Name and symbol name=decoded["name"], symbol=decoded["symbol"], From fdd7d10b05ab0c48ff5fce2ba6efb990dd6552e4 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:17:49 -0700 Subject: [PATCH 08/39] improve `generate_weight_hash` --- bittensor/utils/weight_utils.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index 2a7b7f3afd..e4e1cc50c2 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -389,6 +389,7 @@ def process_weights( return non_zero_weight_uids, normalized_weights +# TODO: In SDKv10 move subuid parameter after netuid and remove default value. Add changes to migration guide. def generate_weight_hash( address: str, netuid: int, @@ -396,25 +397,33 @@ def generate_weight_hash( values: list[int], version_key: int, salt: list[int], + subuid: int = 0, ) -> str: """ Generate a valid commit hash from the provided weights. Args: - address (str): The account identifier. Wallet ss58_address. - netuid (int): The network unique identifier. - uids (list[int]): The list of UIDs. - salt (list[int]): The salt to add to hash. - values (list[int]): The list of weight values. - version_key (int): The version key. + address: The account identifier. Wallet ss58_address. + netuid: The subnet unique identifier. + subuid: The sub-subnet unique identifier. + uids: The list of UIDs. + salt: The salt to add to hash. + values: The list of weight values. + version_key: The version key. Returns: str: The generated commit hash. + + Use `subuid=0` if subnet doesn't have sub-subnets. Actually, means subnet has only one sub-subnet with index 0. """ # Encode data using SCALE codec wallet_address = ScaleBytes(Keypair(ss58_address=address).public_key) netuid = ScaleBytes(netuid.to_bytes(2, "little")) + # If subuid is 0, then subuid is not included in the hash + # for backwards compatibility with old commit hashes on sub-subnets 0 + subuid = b"" if subuid == 0 else ScaleBytes(subuid.to_bytes(2, "little")) + vec_uids = Vec(data=None, sub_type="U16") vec_uids.value = [U16(ScaleBytes(uid.to_bytes(2, "little"))) for uid in uids] uids = ScaleBytes(vec_uids.encode().data) @@ -431,7 +440,7 @@ def generate_weight_hash( vec_salt.value = [U16(ScaleBytes(salts.to_bytes(2, "little"))) for salts in salt] salt = ScaleBytes(vec_salt.encode().data) - data = wallet_address + netuid + uids + values + salt + version_key + data = wallet_address + netuid + subuid + uids + values + salt + version_key # Generate Blake2b hash of the data tuple blake2b_hash = hashlib.blake2b(data.data, digest_size=32) From e8ca0add6b9cdd762526fb222e891f531351f19b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:52:43 -0700 Subject: [PATCH 09/39] update tests --- tests/e2e_tests/test_commit_reveal.py | 3 ++ tests/e2e_tests/test_commit_weights.py | 18 ++++----- tests/e2e_tests/test_hotkeys.py | 31 ++++++++-------- tests/e2e_tests/test_incentive.py | 5 ++- tests/e2e_tests/test_subtensor_functions.py | 19 ++++++++-- tests/e2e_tests/utils/e2e_test_utils.py | 6 +-- tests/unit_tests/test_async_subtensor.py | 40 +++++++------------- tests/unit_tests/test_subtensor.py | 41 ++++++++++----------- tests/unit_tests/utils/test_weight_utils.py | 1 + 9 files changed, 83 insertions(+), 81 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 2bcf5b39da..c575e78a6d 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -181,6 +181,9 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, reveal_round: {expected_reveal_round}" ) + # let chain to update + subtensor.wait_for_block(subtensor.block + 1) + # Fetch current commits pending on the chain commits_on_chain = subtensor.commitments.get_timelocked_weight_commits( netuid=alice_subnet_netuid diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 9953aea85c..81f543ff3c 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -104,8 +104,8 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa # Commit weights success, message = subtensor.commit_weights( - alice_wallet, - netuid, + wallet=alice_wallet, + netuid=netuid, salt=salt, uids=weight_uids, weights=weight_vals, @@ -204,8 +204,8 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # weights sensitive to epoch changes assert sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=local_chain, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={ "netuid": netuid, @@ -215,11 +215,11 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - local_chain, - alice_wallet, - "sudo_set_commit_reveal_weights_enabled", - True, - netuid, + substrate=local_chain, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=True, + netuid=netuid, ), "Unable to enable commit reveal on the subnet" assert subtensor.commit_reveal_enabled(netuid), "Failed to enable commit/reveal" diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 5831e4bf27..479c5f7d08 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -241,8 +241,8 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ) success, error = subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -260,7 +260,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w # children not set yet (have to wait cool-down period) success, children, error = subtensor.get_children( - alice_wallet.hotkey.ss58_address, + hotkey=alice_wallet.hotkey.ss58_address, block=set_children_block, netuid=dave_subnet_netuid, ) @@ -271,7 +271,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w # children are in pending state pending, cooldown = subtensor.get_children_pending( - alice_wallet.hotkey.ss58_address, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -281,7 +281,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2) success, children, error = subtensor.get_children( - alice_wallet.hotkey.ss58_address, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -295,7 +295,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w # pending queue is empty pending, cooldown = subtensor.get_children_pending( - alice_wallet.hotkey.ss58_address, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) assert pending == [] @@ -303,25 +303,26 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w with pytest.raises(TxRateLimitExceeded): set_children_block = subtensor.get_current_block() subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, ) subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, ) - subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + # wait for rate limit to expire + 1 block to ensure that the rate limit is expired + subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 1) subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, @@ -329,7 +330,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w set_children_block = subtensor.get_current_block() pending, cooldown = subtensor.get_children_pending( - alice_wallet.hotkey.ss58_address, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -338,7 +339,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w subtensor.wait_for_block(cooldown) success, children, error = subtensor.get_children( - alice_wallet.hotkey.ss58_address, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index d08fb7ece6..3acc97f0a2 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -5,7 +5,6 @@ from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( sudo_set_admin_utils, - wait_epoch, ) from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call @@ -71,7 +70,9 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa ) # Wait for the first epoch to pass - await wait_epoch(subtensor, alice_subnet_netuid) + subtensor.wait_for_block( + subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 + ) # Get current miner/validator stats alice_neuron = subtensor.neurons(netuid=alice_subnet_netuid)[0] diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 6dbd9805ef..ce0ac738b0 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -6,6 +6,7 @@ from tests.e2e_tests.utils.chain_interactions import ( wait_epoch, ) +from bittensor.core.extrinsics.utils import get_extrinsic_fee from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call """ @@ -70,6 +71,17 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall # Subnet burn cost is increased immediately after a subnet is registered post_subnet_creation_cost = subtensor.get_subnet_burn_cost() + # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={ + "hotkey": alice_wallet.hotkey.ss58_address, + "mechid": 1, + }, + ) + register_fee = get_extrinsic_fee(call, alice_wallet.hotkey, subtensor) + # Assert that the burn cost changed after registering a subnet assert Balance.from_tao(pre_subnet_creation_cost) < Balance.from_tao( post_subnet_creation_cost @@ -77,9 +89,10 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall # Assert amount is deducted once a subnetwork is registered by Alice alice_balance_post_sn = subtensor.get_balance(alice_wallet.coldkeypub.ss58_address) - assert alice_balance_post_sn + pre_subnet_creation_cost == initial_alice_balance, ( - "Balance is the same even after registering a subnet" - ) + assert ( + alice_balance_post_sn + pre_subnet_creation_cost + register_fee + == initial_alice_balance + ), "Balance is the same even after registering a subnet" # Subnet 2 is added after registration assert subtensor.get_subnets() == [0, 1, 2] diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index d41c80982d..744663d08c 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -94,7 +94,7 @@ def __init__(self, dir, wallet, netuid): async def __aenter__(self): env = os.environ.copy() - env["BT_LOGGING_INFO"] = "1" + env["BT_LOGGING_DEBUG"] = "1" self.process = await asyncio.create_subprocess_exec( sys.executable, f"{self.dir}/miner.py", @@ -157,7 +157,7 @@ def __init__(self, dir, wallet, netuid): async def __aenter__(self): env = os.environ.copy() - env["BT_LOGGING_INFO"] = "1" + env["BT_LOGGING_DEBUG"] = "1" self.process = await asyncio.create_subprocess_exec( sys.executable, f"{self.dir}/validator.py", @@ -235,7 +235,7 @@ def wait_to_start_call( in_blocks: int = 10, ): """Waits for a certain number of blocks before making a start call.""" - if subtensor.is_fast_blocks() is False: + if subtensor.chain.is_fast_blocks() is False: in_blocks = 5 bittensor.logging.console.info( f"Waiting for [blue]{in_blocks}[/blue] blocks before [red]start call[/red]. " diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index e1bf1420dd..fd4794af6b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -1,6 +1,6 @@ import datetime import unittest.mock as mock - +import numpy as np import pytest from async_substrate_interface.types import ScaleObj from bittensor_wallet import Wallet @@ -2773,7 +2773,7 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): mocked_set_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success")) mocker.patch.object( - async_subtensor, "set_weights_extrinsic", mocked_set_weights_extrinsic + async_subtensor, "set_sub_weights_extrinsic", mocked_set_weights_extrinsic ) # Call @@ -2803,6 +2803,7 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): wait_for_inclusion=False, weights=fake_weights, period=8, + subuid=0, ) mocked_weights_rate_limit.assert_called_once_with(fake_netuid) assert result is True @@ -2832,7 +2833,7 @@ async def test_set_weights_with_exception(subtensor, fake_wallet, mocker): side_effect=Exception("Test exception") ) mocker.patch.object( - async_subtensor, "set_weights_extrinsic", mocked_set_weights_extrinsic + async_subtensor, "set_sub_weights_extrinsic", mocked_set_weights_extrinsic ) # Call @@ -2865,10 +2866,10 @@ async def test_root_set_weights_success(subtensor, fake_wallet, mocker): async_subtensor, "set_root_weights_extrinsic", mocked_set_root_weights_extrinsic ) - mocked_np_array_netuids = mocker.Mock(autospec=async_subtensor.np.ndarray) - mocked_np_array_weights = mocker.Mock(autospec=async_subtensor.np.ndarray) + mocked_np_array_netuids = mocker.Mock(autospec=np.ndarray) + mocked_np_array_weights = mocker.Mock(autospec=np.ndarray) mocker.patch.object( - async_subtensor.np, + np, "array", side_effect=[mocked_np_array_netuids, mocked_np_array_weights], ) @@ -2905,14 +2906,9 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): fake_weights = [100, 200, 300] max_retries = 3 - mocked_generate_weight_hash = mocker.Mock(return_value="fake_commit_hash") - mocker.patch.object( - async_subtensor, "generate_weight_hash", mocked_generate_weight_hash - ) - mocked_commit_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success")) mocker.patch.object( - async_subtensor, "commit_weights_extrinsic", mocked_commit_weights_extrinsic + async_subtensor, "commit_sub_weights_extrinsic", mocked_commit_weights_extrinsic ) # Call @@ -2926,22 +2922,17 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): ) # Asserts - mocked_generate_weight_hash.assert_called_once_with( - address=fake_wallet.hotkey.ss58_address, - netuid=fake_netuid, - uids=fake_uids, - values=fake_weights, - salt=fake_salt, - version_key=async_subtensor.version_as_int, - ) mocked_commit_weights_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, - commit_hash="fake_commit_hash", + salt=fake_salt, + uids=fake_uids, + weights=fake_weights, wait_for_inclusion=False, wait_for_finalization=False, period=16, + subuid=0, ) assert result is True assert message == "Success" @@ -2957,16 +2948,11 @@ async def test_commit_weights_with_exception(subtensor, fake_wallet, mocker): fake_weights = [100, 200, 300] max_retries = 1 - mocked_generate_weight_hash = mocker.Mock(return_value="fake_commit_hash") - mocker.patch.object( - async_subtensor, "generate_weight_hash", mocked_generate_weight_hash - ) - mocked_commit_weights_extrinsic = mocker.AsyncMock( side_effect=Exception("Test exception") ) mocker.patch.object( - async_subtensor, "commit_weights_extrinsic", mocked_commit_weights_extrinsic + async_subtensor, "commit_sub_weights_extrinsic", mocked_commit_weights_extrinsic ) # Call diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 78d9ffeaa9..e1f12bdd80 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1170,7 +1170,7 @@ def test_set_weights(subtensor, mocker, fake_wallet): subtensor.weights_rate_limit = mocked_weights_rate_limit mocked_set_weights_extrinsic = mocker.patch.object( - subtensor_module, "set_weights_extrinsic", return_value=expected_result + subtensor_module, "set_sub_weights_extrinsic", return_value=expected_result ) # Call @@ -1203,6 +1203,7 @@ def test_set_weights(subtensor, mocker, fake_wallet): wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, period=8, + subuid=0, ) assert result == expected_result @@ -1952,11 +1953,8 @@ def test_commit_weights(subtensor, fake_wallet, mocker): max_retries = 5 expected_result = (True, None) - mocked_generate_weight_hash = mocker.patch.object( - subtensor_module, "generate_weight_hash", return_value=expected_result - ) mocked_commit_weights_extrinsic = mocker.patch.object( - subtensor_module, "commit_weights_extrinsic", return_value=expected_result + subtensor_module, "commit_sub_weights_extrinsic", return_value=expected_result ) # Call @@ -1973,23 +1971,17 @@ def test_commit_weights(subtensor, fake_wallet, mocker): ) # Asserts - mocked_generate_weight_hash.assert_called_once_with( - address=fake_wallet.hotkey.ss58_address, - netuid=netuid, - uids=list(uids), - values=list(weights), - salt=list(salt), - version_key=settings.version_as_int, - ) - mocked_commit_weights_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - commit_hash=mocked_generate_weight_hash.return_value, + salt=salt, + uids=uids, + weights=weights, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=16, + subuid=0, ) assert result == expected_result @@ -2003,7 +1995,7 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): salt = [4, 2, 2, 1] expected_result = (True, None) mocked_extrinsic = mocker.patch.object( - subtensor_module, "reveal_weights_extrinsic", return_value=expected_result + subtensor_module, "reveal_sub_weights_extrinsic", return_value=expected_result ) # Call @@ -2030,6 +2022,7 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): wait_for_inclusion=False, wait_for_finalization=False, period=16, + subuid=0, ) @@ -2045,7 +2038,9 @@ def test_reveal_weights_false(subtensor, fake_wallet, mocker): False, "No attempt made. Perhaps it is too soon to reveal weights!", ) - mocked_extrinsic = mocker.patch.object(subtensor_module, "reveal_weights_extrinsic") + mocked_extrinsic = mocker.patch.object( + subtensor_module, "reveal_sub_weights_extrinsic" + ) # Call result = subtensor.reveal_weights( @@ -3150,10 +3145,10 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): mocked_commit_reveal_enabled = mocker.patch.object( subtensor, "commit_reveal_enabled", return_value=True ) - mocked_commit_reveal_v3_extrinsic = mocker.patch.object( - subtensor_module, "commit_reveal_v3_extrinsic" + mocked_commit_timelocked_sub_weights_extrinsic = mocker.patch.object( + subtensor_module, "commit_timelocked_sub_weights_extrinsic" ) - mocked_commit_reveal_v3_extrinsic.return_value = ( + mocked_commit_timelocked_sub_weights_extrinsic.return_value = ( True, "Weights committed successfully", ) @@ -3172,7 +3167,7 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): # Asserts mocked_commit_reveal_enabled.assert_called_once_with(netuid=fake_netuid) - mocked_commit_reveal_v3_extrinsic.assert_called_once_with( + mocked_commit_timelocked_sub_weights_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -3183,8 +3178,10 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): wait_for_finalization=fake_wait_for_finalization, block_time=12.0, period=8, + commit_reveal_version=4, + subuid=0, ) - assert result == mocked_commit_reveal_v3_extrinsic.return_value + assert result == mocked_commit_timelocked_sub_weights_extrinsic.return_value def test_connection_limit(mocker): diff --git a/tests/unit_tests/utils/test_weight_utils.py b/tests/unit_tests/utils/test_weight_utils.py index c378173bfa..204c2c7ad4 100644 --- a/tests/unit_tests/utils/test_weight_utils.py +++ b/tests/unit_tests/utils/test_weight_utils.py @@ -643,6 +643,7 @@ def test_generate_weight_hash(mocker): result = weight_utils.generate_weight_hash( address=fake_address, netuid=fake_netuid, + subuid=0, uids=fake_uids, values=fake_values, version_key=fake_version_key, From 37933a449a9f0953b67008604026fc42cadef7ab Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 20:52:58 -0700 Subject: [PATCH 10/39] update SubtensorApi --- bittensor/core/subtensor_api/chain.py | 1 + bittensor/core/subtensor_api/metagraphs.py | 3 +++ bittensor/core/subtensor_api/subnets.py | 1 + bittensor/core/subtensor_api/utils.py | 7 +++++++ 4 files changed, 12 insertions(+) diff --git a/bittensor/core/subtensor_api/chain.py b/bittensor/core/subtensor_api/chain.py index 086e7d48d4..9f4c312f13 100644 --- a/bittensor/core/subtensor_api/chain.py +++ b/bittensor/core/subtensor_api/chain.py @@ -15,6 +15,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_minimum_required_stake = subtensor.get_minimum_required_stake self.get_vote_data = subtensor.get_vote_data self.get_timestamp = subtensor.get_timestamp + self.is_in_admin_freeze_window = subtensor.is_in_admin_freeze_window self.is_fast_blocks = subtensor.is_fast_blocks self.last_drand_round = subtensor.last_drand_round self.state_call = subtensor.state_call diff --git a/bittensor/core/subtensor_api/metagraphs.py b/bittensor/core/subtensor_api/metagraphs.py index af143a1620..aa7b2fe2ee 100644 --- a/bittensor/core/subtensor_api/metagraphs.py +++ b/bittensor/core/subtensor_api/metagraphs.py @@ -9,4 +9,7 @@ class Metagraphs: def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_metagraph_info = subtensor.get_metagraph_info self.get_all_metagraphs_info = subtensor.get_all_metagraphs_info + self.get_sub_all_metagraphs = subtensor.get_sub_all_metagraphs + self.get_sub_metagraph_info = subtensor.get_sub_metagraph_info + self.get_sub_selective_metagraph = subtensor.get_sub_selective_metagraph self.metagraph = subtensor.metagraph diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index cfdee525b8..5d2793aa14 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -25,6 +25,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): subtensor.get_neuron_for_pubkey_and_subnet ) self.get_next_epoch_start_block = subtensor.get_next_epoch_start_block + self.get_sub_subnet_count = subtensor.get_sub_subnet_count self.get_subnet_burn_cost = subtensor.get_subnet_burn_cost self.get_subnet_hyperparameters = subtensor.get_subnet_hyperparameters self.get_subnet_info = subtensor.get_subnet_info diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 3fdfe2b445..6dac1425cf 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -92,6 +92,12 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_stake_movement_fee = subtensor._subtensor.get_stake_movement_fee subtensor.get_stake_operations_fee = subtensor._subtensor.get_stake_operations_fee subtensor.get_stake_weight = subtensor._subtensor.get_stake_weight + subtensor.get_sub_all_metagraphs = subtensor._subtensor.get_sub_all_metagraphs + subtensor.get_sub_metagraph_info = subtensor._subtensor.get_sub_metagraph_info + subtensor.get_sub_selective_metagraph = ( + subtensor._subtensor.get_sub_selective_metagraph + ) + subtensor.get_sub_subnet_count = subtensor._subtensor.get_sub_subnet_count subtensor.get_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( subtensor._subtensor.get_subnet_hyperparameters @@ -126,6 +132,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.is_hotkey_registered_on_subnet = ( subtensor._subtensor.is_hotkey_registered_on_subnet ) + subtensor.is_in_admin_freeze_window = subtensor._subtensor.is_in_admin_freeze_window subtensor.is_subnet_active = subtensor._subtensor.is_subnet_active subtensor.last_drand_round = subtensor._subtensor.last_drand_round subtensor.log_verbose = subtensor._subtensor.log_verbose From 95c263fc91f64308b988ce51ef4eaecb71de2680 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 12 Sep 2025 21:05:24 -0700 Subject: [PATCH 11/39] update docstrings --- bittensor/core/async_subtensor.py | 18 ++++++++++++++---- .../core/extrinsics/asyncex/sub_subnet.py | 4 ++-- bittensor/core/subtensor.py | 16 ++++++++++++---- bittensor/utils/weight_utils.py | 2 +- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 2ac30b0e7c..6cfcff70a9 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2705,7 +2705,7 @@ async def get_sub_metagraph_info( """ Retrieves metagraph information for the specified sub-subnet (netuid, subuid). - Arguments: + Parameters: netuid: Subnet identifier. subuid: Sub-subnet identifier. block: The blockchain block number for the query. @@ -2741,7 +2741,7 @@ async def get_sub_selective_metagraph( """ Retrieves selective metagraph information for the specified sub-subnet (netuid, subuid). - Arguments: + Parameters: netuid: Subnet identifier. subuid: Sub-subnet identifier. field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. @@ -2777,7 +2777,17 @@ async def get_sub_subnet_count( block_hash: Optional[str] = None, reuse_block: bool = False, ) -> int: - """Returns number of subnets""" + """Retrieves the number of sub-subnets for provided subnet. + + Parameters: + netuid: Subnet identifier. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + The number of sub-subnets for provided subnet. + """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) query = await self.substrate.query( module="SubtensorModule", @@ -3544,7 +3554,7 @@ async def is_in_admin_freeze_window( for the given subnet. During this window, admin ops are prohibited to avoid interference with validator weight submissions. - Args: + Parameters: netuid (int): The unique identifier of the subnet. block (Optional[int]): The blockchain block number for the query. block_hash: The blockchain block_hash representation of the block id. diff --git a/bittensor/core/extrinsics/asyncex/sub_subnet.py b/bittensor/core/extrinsics/asyncex/sub_subnet.py index 2bcf214cdf..1deac2f0b4 100644 --- a/bittensor/core/extrinsics/asyncex/sub_subnet.py +++ b/bittensor/core/extrinsics/asyncex/sub_subnet.py @@ -230,7 +230,7 @@ async def reveal_sub_weights_extrinsic( """ Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. - Args: + Parameters: subtensor: AsyncSubtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. @@ -314,7 +314,7 @@ async def set_sub_weights_extrinsic( """ Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. - Args: + Parameters: subtensor: AsyncSubtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a201ee28b2..721fd3a714 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1906,7 +1906,7 @@ def get_sub_metagraph_info( """ Retrieves metagraph information for the specified sub-subnet (netuid, subuid). - Arguments: + Parameters: netuid: Subnet identifier. subuid: Sub-subnet identifier. block: The blockchain block number for the query. @@ -1939,7 +1939,7 @@ def get_sub_selective_metagraph( """ Retrieves selective metagraph information for the specified sub-subnet (netuid, subuid). - Arguments: + Parameters: netuid: Subnet identifier. subuid: Sub-subnet identifier. field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. @@ -1971,7 +1971,15 @@ def get_sub_subnet_count( netuid: int, block: Optional[int] = None, ) -> int: - """Returns number of subnets""" + """Retrieves the number of sub-subnets for provided subnet. + + Parameters: + netuid: Subnet identifier. + block: The blockchain block number for the query. + + Returns: + The number of sub-subnets for provided subnet. + """ block_hash = self.determine_block_hash(block) query = self.substrate.query( module="SubtensorModule", @@ -2587,7 +2595,7 @@ def is_in_admin_freeze_window( for the given subnet. During this window, admin ops are prohibited to avoid interference with validator weight submissions. - Args: + Parameters: netuid (int): The unique identifier of the subnet. block (Optional[int]): The blockchain block number for the query. diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index e4e1cc50c2..ebca62db22 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -402,7 +402,7 @@ def generate_weight_hash( """ Generate a valid commit hash from the provided weights. - Args: + Parameters: address: The account identifier. Wallet ss58_address. netuid: The subnet unique identifier. subuid: The sub-subnet unique identifier. From 2f8487571ea383e28a8572c132bc7348f875ffcc Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 11:35:47 -0700 Subject: [PATCH 12/39] add unit tests --- bittensor/core/async_subtensor.py | 27 +-- bittensor/core/subtensor.py | 23 +-- tests/unit_tests/test_async_subtensor.py | 251 +++++++++++++++++++++++ tests/unit_tests/test_subtensor.py | 239 +++++++++++++++++++++ 4 files changed, 512 insertions(+), 28 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 6cfcff70a9..4b1f7b5c96 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2689,10 +2689,10 @@ async def get_sub_all_metagraphs( method="get_all_submetagraphs", block_hash=block_hash, ) - if query.value is None: + if query is None or query.value is None: return None - return MetagraphInfo.from_dict(query.value) + return MetagraphInfo.list_from_dicts(query.value) async def get_sub_metagraph_info( self, @@ -2723,8 +2723,8 @@ async def get_sub_metagraph_info( params=[netuid, subuid], block_hash=block_hash, ) - if query.value is None: - logging.error(f"Sub-subnet #{netuid}.{subuid} does not exist.") + if query is None or query.value is None: + logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") return None return MetagraphInfo.from_dict(query.value) @@ -2764,8 +2764,8 @@ async def get_sub_selective_metagraph( params=[netuid, subuid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) - if query.value is None: - logging.error(f"Subnet #{netuid}.{subuid} does not exist.") + if query is None or query.value is None: + logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") return None return MetagraphInfo.from_dict(query.value) @@ -3563,13 +3563,11 @@ async def is_in_admin_freeze_window( Returns: bool: True if in freeze window, else False. """ - tempo, next_epoch_start_block, window = await asyncio.gather( - self.tempo( - netuid=netuid, - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ), + # SN0 doesn't have admin_freeze_window + if netuid == 0: + return False + + next_epoch_start_block, window = await asyncio.gather( self.get_next_epoch_start_block( netuid=netuid, block=block, @@ -3580,9 +3578,6 @@ async def is_in_admin_freeze_window( block=block, block_hash=block_hash, reuse_block=reuse_block ), ) - # SN0 doesn't have admin_freeze_window - if tempo == 0: - return False if next_epoch_start_block is not None: remaining = next_epoch_start_block - await self.block diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 721fd3a714..f74dd835be 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1892,10 +1892,10 @@ def get_sub_all_metagraphs( method="get_all_submetagraphs", block_hash=block_hash, ) - if query.value is None: + if query is None or query.value is None: return None - return MetagraphInfo.from_dict(query.value) + return MetagraphInfo.list_from_dicts(query.value) def get_sub_metagraph_info( self, @@ -1918,13 +1918,13 @@ def get_sub_metagraph_info( block_hash = self.determine_block_hash(block) query = self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_submetagraph", + api="SubnetInfoRuntimeApi", + method="get_submetagraph", params=[netuid, subuid], block_hash=block_hash, ) - if query.value is None: - logging.error(f"Sub-subnet #{netuid}.{subuid} does not exist.") + if query is None or query.value is None: + logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") return None return MetagraphInfo.from_dict(query.value) @@ -1955,13 +1955,13 @@ def get_sub_selective_metagraph( for f in field_indices ] query = self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_selective_submetagraph", + api="SubnetInfoRuntimeApi", + method="get_selective_submetagraph", params=[netuid, subuid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) - if query.value is None: - logging.error(f"Subnet #{netuid}.{subuid} does not exist.") + if query is None or query.value is None: + logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") return None return MetagraphInfo.from_dict(query.value) @@ -2602,9 +2602,8 @@ def is_in_admin_freeze_window( Returns: bool: True if in freeze window, else False. """ - tempo = self.tempo(netuid, block=block) # SN0 doesn't have admin_freeze_window - if tempo == 0: + if netuid == 0: return False next_epoch_start_block = self.get_next_epoch_start_block( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index fd4794af6b..5f380c3616 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4137,3 +4137,254 @@ async def test_get_timelocked_weight_commits(subtensor, mocker): block_hash=mock_determine_block_hash.return_value, ) assert result == [] + + +@pytest.mark.asyncio +async def test_get_sub_all_metagraphs_returns_none(subtensor, mocker): + """Verify that `get_sub_all_metagraphs` method returns None.""" + # Preps + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=None) + mocked_metagraph_info_list_from_dicts = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" + ) + + # Call + result = await subtensor.get_sub_all_metagraphs() + + # Asserts + subtensor.substrate.runtime_call.assert_awaited_once_with( + api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + ) + mocked_metagraph_info_list_from_dicts.assert_not_called() + assert result is None + + +@pytest.mark.asyncio +async def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): + """Verify that `get_sub_all_metagraphs` method processed the data correctly.""" + # Preps + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_list_from_dicts = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" + ) + + # Call + result = await subtensor.get_sub_all_metagraphs() + + # Asserts + subtensor.substrate.runtime_call.assert_awaited_once_with( + api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + ) + mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) + assert result == mocked_metagraph_info_list_from_dicts.return_value + + +@pytest.mark.asyncio +async def test_get_sub_metagraph_info_returns_none(subtensor, mocker): + """Verify that `get_sub_metagraph_info` method returns None.""" + # Preps + netuid = 14 + subuid = 5 + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = None + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = await subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_awaited_once_with( + api="SubnetInfoRuntimeApi", + method="get_submetagraph", + params=[netuid, subuid], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result is mocked_result + mocked_metagraph_info_from_dict.assert_not_called() + + +@pytest.mark.asyncio +async def test_get_sub_metagraph_info_happy_path(subtensor, mocker): + """Verify that `get_sub_metagraph_info` method processed the data correctly.""" + # Preps + netuid = 14 + subuid = 5 + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = await subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_awaited_once_with( + api="SubnetInfoRuntimeApi", + method="get_submetagraph", + params=[netuid, subuid], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) + assert result is mocked_metagraph_info_from_dict.return_value + + +@pytest.mark.asyncio +async def test_get_sub_selective_metagraph_returns_none(subtensor, mocker): + """Verify that `get_sub_selective_metagraph` method returns None.""" + # Preps + netuid = 14 + subuid = 5 + field_indices = [0, 1, 73] + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = None + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = await subtensor.get_sub_selective_metagraph( + netuid=netuid, subuid=subuid, field_indices=field_indices + ) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_awaited_once_with( + api="SubnetInfoRuntimeApi", + method="get_selective_submetagraph", + params=[netuid, subuid, field_indices], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result is mocked_result + mocked_metagraph_info_from_dict.assert_not_called() + + +@pytest.mark.asyncio +async def test_get_sub_selective_metagraph_happy_path(subtensor, mocker): + """Verify that `get_sub_selective_metagraph` method processed the data correctly.""" + # Preps + netuid = 14 + subuid = 5 + field_indices = [0, 1, 73] + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = await subtensor.get_sub_selective_metagraph( + netuid=netuid, subuid=subuid, field_indices=field_indices + ) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_awaited_once_with( + api="SubnetInfoRuntimeApi", + method="get_selective_submetagraph", + params=[netuid, subuid, field_indices], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) + assert result is mocked_metagraph_info_from_dict.return_value + + +@pytest.mark.asyncio +async def test_get_sub_subnet_count(subtensor, mocker): + """Verify that `get_sub_subnet_count` method processed the data correctly.""" + # Preps + netuid = 14 + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_query = mocker.patch.object(subtensor.substrate, "query") + + # Call + result = await subtensor.get_sub_subnet_count(netuid=netuid) + + # Asserts + mocked_determine_block_hash.assert_called_once() + mocked_query.assert_called_once_with( + module="SubtensorModule", + storage_function="SubsubnetCountCurrent", + params=[netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result is mocked_query.return_value.value + + +@pytest.mark.asyncio +async def test_is_in_admin_freeze_window_root_net(subtensor, mocker): + """Verify that root net has no admin freeze window.""" + # Preps + netuid = 0 + mocked_get_next_epoch_start_block = mocker.patch.object( + subtensor, "get_next_epoch_start_block" + ) + + # Call + result = await subtensor.is_in_admin_freeze_window(netuid=netuid) + + # Asserts + mocked_get_next_epoch_start_block.assert_not_called() + assert result is False + + +@pytest.mark.parametrize( + "block, next_esb, expected_result", + ( + [89, 100, False], + [90, 100, False], + [91, 100, True], + ), +) +@pytest.mark.asyncio +async def test_is_in_admin_freeze_window( + subtensor, mocker, block, next_esb, expected_result +): + """Verify that `is_in_admin_freeze_window` method processed the data correctly.""" + # Preps + netuid = 14 + mocker.patch.object(subtensor, "get_current_block", return_value=block) + mocker.patch.object(subtensor, "get_next_epoch_start_block", return_value=next_esb) + mocker.patch.object(subtensor, "get_admin_freeze_window", return_value=10) + + # Call + + result = await subtensor.is_in_admin_freeze_window(netuid=netuid) + + # Asserts + assert result is expected_result + + +@pytest.mark.asyncio +async def test_get_admin_freeze_window(subtensor, mocker): + """Verify that `get_admin_freeze_window` calls proper methods.""" + # Preps + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_query = mocker.patch.object(subtensor.substrate, "query") + + # Call + result = await subtensor.get_admin_freeze_window() + + # Asserts + mocked_query.assert_awaited_once_with( + module="SubtensorModule", + storage_function="AdminFreezeWindow", + block_hash=mocked_determine_block_hash.return_value, + ) + assert result == mocked_query.return_value.value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index e1f12bdd80..7eec31b57f 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4330,3 +4330,242 @@ def test_get_timelocked_weight_commits(subtensor, mocker): block_hash=mock_determine_block_hash.return_value, ) assert result == [] + + +def test_get_sub_all_metagraphs_returns_none(subtensor, mocker): + """Verify that `get_sub_all_metagraphs` method returns None.""" + # Preps + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=None) + mocked_metagraph_info_list_from_dicts = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" + ) + + # Call + result = subtensor.get_sub_all_metagraphs() + + # Asserts + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + ) + mocked_metagraph_info_list_from_dicts.assert_not_called() + assert result is None + + +def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): + """Verify that `get_sub_all_metagraphs` method processed the data correctly.""" + # Preps + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_list_from_dicts = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" + ) + + # Call + result = subtensor.get_sub_all_metagraphs() + + # Asserts + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + ) + mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) + assert result == mocked_metagraph_info_list_from_dicts.return_value + + +def test_get_sub_metagraph_info_returns_none(subtensor, mocker): + """Verify that `get_sub_metagraph_info` method returns None.""" + # Preps + netuid = 14 + subuid = 5 + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = None + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", + method="get_submetagraph", + params=[netuid, subuid], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result is mocked_result + mocked_metagraph_info_from_dict.assert_not_called() + + +def test_get_sub_metagraph_info_happy_path(subtensor, mocker): + """Verify that `get_sub_metagraph_info` method processed the data correctly.""" + # Preps + netuid = 14 + subuid = 5 + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", + method="get_submetagraph", + params=[netuid, subuid], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) + assert result is mocked_metagraph_info_from_dict.return_value + + +def test_get_sub_selective_metagraph_returns_none(subtensor, mocker): + """Verify that `get_sub_selective_metagraph` method returns None.""" + # Preps + netuid = 14 + subuid = 5 + field_indices = [0, 1, 73] + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = None + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = subtensor.get_sub_selective_metagraph( + netuid=netuid, subuid=subuid, field_indices=field_indices + ) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", + method="get_selective_submetagraph", + params=[netuid, subuid, field_indices], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result is mocked_result + mocked_metagraph_info_from_dict.assert_not_called() + + +def test_get_sub_selective_metagraph_happy_path(subtensor, mocker): + """Verify that `get_sub_selective_metagraph` method processed the data correctly.""" + # Preps + netuid = 14 + subuid = 5 + field_indices = [0, 1, 73] + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_metagraph_info_from_dict = mocker.patch( + "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" + ) + + # Call + result = subtensor.get_sub_selective_metagraph( + netuid=netuid, subuid=subuid, field_indices=field_indices + ) + + # Asserts + mocked_determine_block_hash.assert_called_once() + subtensor.substrate.runtime_call.assert_called_once_with( + api="SubnetInfoRuntimeApi", + method="get_selective_submetagraph", + params=[netuid, subuid, field_indices], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) + assert result is mocked_metagraph_info_from_dict.return_value + + +def test_get_sub_subnet_count(subtensor, mocker): + """Verify that `get_sub_subnet_count` method processed the data correctly.""" + # Preps + netuid = 14 + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_result = mocker.MagicMock() + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) + mocked_query = mocker.patch.object(subtensor.substrate, "query") + + # Call + result = subtensor.get_sub_subnet_count(netuid=netuid) + + # Asserts + mocked_determine_block_hash.assert_called_once() + mocked_query.assert_called_once_with( + module="SubtensorModule", + storage_function="SubsubnetCountCurrent", + params=[netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result is mocked_query.return_value.value + + +def test_is_in_admin_freeze_window_root_net(subtensor, mocker): + """Verify that root net has no admin freeze window.""" + # Preps + netuid = 0 + mocked_get_next_epoch_start_block = mocker.patch.object( + subtensor, "get_next_epoch_start_block" + ) + + # Call + result = subtensor.is_in_admin_freeze_window(netuid=netuid) + + # Asserts + mocked_get_next_epoch_start_block.assert_not_called() + assert result is False + + +@pytest.mark.parametrize( + "block, next_esb, expected_result", + ( + [89, 100, False], + [90, 100, False], + [91, 100, True], + ), +) +def test_is_in_admin_freeze_window(subtensor, mocker, block, next_esb, expected_result): + """Verify that `is_in_admin_freeze_window` method processed the data correctly.""" + # Preps + netuid = 14 + mocker.patch.object(subtensor, "get_current_block", return_value=block) + mocker.patch.object(subtensor, "get_next_epoch_start_block", return_value=next_esb) + mocker.patch.object(subtensor, "get_admin_freeze_window", return_value=10) + + # Call + + result = subtensor.is_in_admin_freeze_window(netuid=netuid) + + # Asserts + assert result is expected_result + + +def test_get_admin_freeze_window(subtensor, mocker): + """Verify that `get_admin_freeze_window` calls proper methods.""" + # Preps + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_query = mocker.patch.object(subtensor.substrate, "query") + + # Call + result = subtensor.get_admin_freeze_window() + + # Asserts + mocked_query.assert_called_once_with( + module="SubtensorModule", + storage_function="AdminFreezeWindow", + block_hash=mocked_determine_block_hash.return_value, + ) + assert result == mocked_query.return_value.value From cf4490673095e30714a1a1fc9855b09656d92ca7 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 12:37:38 -0700 Subject: [PATCH 13/39] improve `subtensor.weights` (use subuid) --- bittensor/core/async_subtensor.py | 6 +++++- bittensor/core/subtensor.py | 7 +++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4b1f7b5c96..3ab61d9ade 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -108,6 +108,7 @@ u16_normalized_float, u64_normalized_float, get_transfer_fn_params, + get_sub_subnet_storage_index, ) from bittensor.utils import deprecated_message from bittensor.utils.balance import ( @@ -4290,6 +4291,7 @@ async def handler(block_data: dict): async def weights( self, netuid: int, + subuid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, @@ -4301,6 +4303,7 @@ async def weights( Arguments: netuid: The network UID of the subnet to query. + subuid: Sub-subnet identifier. block: Block number for synchronization, or `None` for the latest block. block_hash: The hash of the blockchain block for the query. reuse_block: reuse the last-used blockchain block hash. @@ -4311,12 +4314,13 @@ async def weights( The weight distribution is a key factor in the network's consensus algorithm and the ranking of neurons, influencing their influence and reward allocation within the subnet. """ + storage_index = get_sub_subnet_storage_index(netuid, subuid) block_hash = await self.determine_block_hash(block, block_hash, reuse_block) # TODO look into seeing if we can speed this up with storage query w_map_encoded = await self.substrate.query_map( module="SubtensorModule", storage_function="Weights", - params=[netuid], + params=[storage_index], block_hash=block_hash, reuse_block_hash=reuse_block, ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f74dd835be..3a88aede5a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -111,6 +111,7 @@ u64_normalized_float, deprecated_message, get_transfer_fn_params, + get_sub_subnet_storage_index, ) from bittensor.utils.balance import ( Balance, @@ -3119,7 +3120,7 @@ def handler(block_data: dict): return True def weights( - self, netuid: int, block: Optional[int] = None + self, netuid: int, subuid: int = 0, block: Optional[int] = None ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -3128,6 +3129,7 @@ def weights( Arguments: netuid (int): The network UID of the subnet to query. + subuid: Sub-subnet identifier. block (Optional[int]): Block number for synchronization, or ``None`` for the latest block. Returns: @@ -3136,10 +3138,11 @@ def weights( The weight distribution is a key factor in the network's consensus algorithm and the ranking of neurons, influencing their influence and reward allocation within the subnet. """ + storage_index = get_sub_subnet_storage_index(netuid, subuid) w_map_encoded = self.substrate.query_map( module="SubtensorModule", storage_function="Weights", - params=[netuid], + params=[storage_index], block_hash=self.determine_block_hash(block), ) w_map = [(uid, w.value or []) for uid, w in w_map_encoded] From e0fb27f135fb3322ad757b45fd11878d01617b1e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 12:39:01 -0700 Subject: [PATCH 14/39] improve e2e test --- tests/e2e_tests/test_set_weights.py | 114 ++++++++++++++++------------ 1 file changed, 64 insertions(+), 50 deletions(-) diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index b433a97355..0e8e3a95c6 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -2,6 +2,10 @@ import pytest import retry +from bittensor.core.extrinsics.sudo import ( + sudo_set_sub_subnet_count_extrinsic, + sudo_set_admin_freez_window, +) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit @@ -27,35 +31,34 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" + assert sudo_set_admin_freez_window( + subtensor=subtensor, + wallet=alice_wallet, + window=0, + ) netuids = [2, 3] - subnet_tempo = 50 - BLOCK_TIME = 0.25 # 12 for non-fast-block, 0.25 for fast block + TESTED_SUB_SUBNETS = 2 + + # 12 for non-fast-block, 0.25 for fast block + block_time, subnet_tempo = ( + (0.25, 50) if subtensor.chain.is_fast_blocks() else (12.0, 20) + ) print("Testing test_set_weights_uses_next_nonce") # Lower the network registration rate limit and cost sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_network_rate_limit", call_params={"rate_limit": "0"}, # No limit ) # Set lock reduction interval sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_lock_reduction_interval", call_params={"interval": "1"}, # 1 block # reduce lock every block ) @@ -63,7 +66,7 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) # Try to register the subnets for _ in netuids: assert subtensor.register_subnet( - alice_wallet, + wallet=alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ), "Unable to register the subnet" @@ -74,14 +77,20 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) # weights sensitive to epoch changes assert sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={ "netuid": netuid, "tempo": subnet_tempo, }, ) + assert sudo_set_sub_subnet_count_extrinsic( + subtensor=subtensor, + wallet=alice_wallet, + netuid=netuid, + sub_count=2, + ) # make sure 2 epochs are passed subtensor.wait_for_block(subnet_tempo * 2 + 1) @@ -89,20 +98,20 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) # Stake to become to top neuron after the first epoch for netuid in netuids: subtensor.add_stake( - alice_wallet, - alice_wallet.hotkey.ss58_address, - netuid, - Balance.from_tao(10_000), + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=netuid, + amount=Balance.from_tao(10_000), ) # Set weight hyperparameters per subnet for netuid in netuids: assert sudo_set_hyperparameter_bool( - local_chain, - alice_wallet, - "sudo_set_commit_reveal_weights_enabled", - False, - netuid, + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=False, + netuid=netuid, ), "Unable to enable commit reveal on the subnet" assert not subtensor.commit_reveal_enabled( @@ -115,8 +124,8 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) # Lower set weights rate limit status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, ) @@ -145,15 +154,17 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) # 3 time doing call if nonce wasn't updated, then raise error @retry.retry(exceptions=Exception, tries=3, delay=1) @execute_and_wait_for_next_nonce(subtensor=subtensor, wallet=alice_wallet) - def set_weights(netuid_): + def set_weights(netuid_, subuid_): success, message = subtensor.set_weights( wallet=alice_wallet, netuid=netuid_, + subuid=subuid_, uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, wait_for_finalization=False, period=subnet_tempo, + block_time=block_time, ) assert success is True, message @@ -162,22 +173,25 @@ def set_weights(netuid_): f"{subtensor.substrate.get_account_next_index(alice_wallet.hotkey.ss58_address)}[/orange]" ) - # Set weights for each subnet - for netuid in netuids: - set_weights(netuid) - - for netuid in netuids: - # Query the Weights storage map for all three subnets - query = subtensor.query_module( - module="SubtensorModule", - name="Weights", - params=[netuid, 0], # Alice should be the only UID - ) - - weights = query.value - logging.console.info(f"Weights for subnet {netuid}: {weights}") - - assert weights is not None, f"Weights not found for subnet {netuid}" - assert weights == list(zip(weight_uids, weight_vals)), ( - f"Weights do not match for subnet {netuid}" - ) + for subuid in range(TESTED_SUB_SUBNETS): + # Set weights for each subnet + for netuid in netuids: + set_weights(netuid, subuid) + + for netuid in netuids: + # Query the Weights storage map for all three subnets + weights = subtensor.subnets.weights( + netuid=netuid, + subuid=subuid, + ) + alice_weights = weights[0][1] + logging.console.info( + f"Weights for sub-subnet {netuid}.{subuid}: {alice_weights}" + ) + + assert alice_weights is not None, ( + f"Weights not found for sub-subnet {netuid}.{subuid}" + ) + assert alice_weights == list(zip(weight_uids, weight_vals)), ( + f"Weights do not match for subnet {netuid}" + ) From 3883f7cc858bf412481eb6890144dcb9970e6681 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 15:26:13 -0700 Subject: [PATCH 15/39] fix `sudo_call_extrinsic` --- bittensor/core/extrinsics/asyncex/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index a7322bba6e..d103b38f0b 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -83,7 +83,7 @@ async def sudo_call_extrinsic( call_module="Sudo", call_function="sudo", call_params={ - "call": subtensor.substrate.compose_call( + "call": await subtensor.substrate.compose_call( call_module=call_module, call_function=call_function, call_params=call_params, From 53df6e7742be12513e64fd905f569638c2d5af51 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 15:26:55 -0700 Subject: [PATCH 16/39] update `get_timelocked_weight_commits` method --- bittensor/core/async_subtensor.py | 9 ++++++--- bittensor/core/subtensor.py | 13 ++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 49623ed0f7..463969eb62 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2919,6 +2919,7 @@ async def get_subnet_prices( async def get_timelocked_weight_commits( self, netuid: int, + subuid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, @@ -2926,8 +2927,9 @@ async def get_timelocked_weight_commits( """ Retrieves CRv4 weight commit information for a specific subnet. - Arguments: - netuid (int): The unique identifier of the subnet. + Parameters: + netuid: Subnet identifier. + subuid: Sub-subnet identifier. block (Optional[int]): The blockchain block number for the query. Default is ``None``. block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block @@ -2942,13 +2944,14 @@ async def get_timelocked_weight_commits( The list may be empty if there are no commits found. """ + storage_index = get_sub_subnet_storage_index(netuid, subuid) block_hash = await self.determine_block_hash( block=block, block_hash=block_hash, reuse_block=reuse_block ) result = await self.substrate.query_map( module="SubtensorModule", storage_function="TimelockedWeightCommits", - params=[netuid], + params=[storage_index], block_hash=block_hash, ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 626e074cc3..eb10e7c3e7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1363,6 +1363,7 @@ def get_minimum_required_stake(self) -> Balance: return Balance.from_rao(getattr(result, "value", 0)) + # TODO: metagraph_info and selective_metagraph_info logic should be separated in SDKv10 (2 methods) def get_metagraph_info( self, netuid: int, @@ -2087,14 +2088,15 @@ def get_subnet_prices( return prices def get_timelocked_weight_commits( - self, netuid: int, block: Optional[int] = None + self, netuid: int, subuid: int = 0, block: Optional[int] = None ) -> list[tuple[str, int, str, int]]: """ Retrieves CRv4 weight commit information for a specific subnet. - Arguments: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. Default is ``None``. + Parameters: + netuid: Subnet identifier. + subuid: Sub-subnet identifier. + block: The blockchain block number for the query. Default is ``None``. Returns: A list of commit details, where each item contains: @@ -2105,10 +2107,11 @@ def get_timelocked_weight_commits( The list may be empty if there are no commits found. """ + storage_index = get_sub_subnet_storage_index(netuid, subuid) result = self.substrate.query_map( module="SubtensorModule", storage_function="TimelockedWeightCommits", - params=[netuid], + params=[storage_index], block_hash=self.determine_block_hash(block=block), ) From 3749555637fe8f7fd6b33689546da2b116548ce2 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 15:33:16 -0700 Subject: [PATCH 17/39] update unit and e2e tests --- tests/e2e_tests/test_commit_reveal.py | 421 ++++++++++++----------- tests/unit_tests/test_async_subtensor.py | 2 +- tests/unit_tests/test_subtensor.py | 2 +- 3 files changed, 231 insertions(+), 194 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index c575e78a6d..bb736ee48c 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -1,5 +1,6 @@ import re - +import time +import asyncio import numpy as np import pytest @@ -12,6 +13,15 @@ wait_interval, next_tempo, ) +from bittensor.core.extrinsics.sudo import ( + sudo_set_sub_subnet_count_extrinsic, + sudo_set_admin_freez_window, +) + +from bittensor.core.extrinsics.asyncex.sudo import ( + sudo_set_sub_subnet_count_extrinsic as async_sudo_set_sub_subnet_count_extrinsic, + sudo_set_admin_freez_window as async_sudo_set_admin_freez_window, +) # @pytest.mark.parametrize("local_chain", [True], indirect=True) @@ -31,25 +41,18 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" - - logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") + assert sudo_set_admin_freez_window(subtensor, alice_wallet, 0) # 12 for non-fast-block, 0.25 for fast block BLOCK_TIME, TEMPO_TO_SET = ( (0.25, 100) if subtensor.chain.is_fast_blocks() else (12.0, 20) ) + TESTED_SUB_SUBNETS = 3 + logging.console.info(f"Using block time: {BLOCK_TIME}") alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 @@ -64,6 +67,10 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle f"SN #{alice_subnet_netuid} wasn't created successfully" ) + assert sudo_set_sub_subnet_count_extrinsic( + subtensor, alice_wallet, alice_subnet_netuid, TESTED_SUB_SUBNETS + ), "Cannot create sub-subnets." + logging.console.success(f"SN #{alice_subnet_netuid} is registered.") # Enable commit_reveal on the subnet @@ -157,91 +164,110 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" ) - # commit_block is the block when weights were committed on the chain (transaction block) - expected_commit_block = subtensor.block + 1 - # Commit weights - success, message = subtensor.extrinsics.set_weights( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - uids=weight_uids, - weights=weight_vals, - wait_for_inclusion=True, - wait_for_finalization=True, - block_time=BLOCK_TIME, - period=16, - ) + for subuid in range(TESTED_SUB_SUBNETS): + logging.console.info( + f"[magenta]Testing sub-subnet: {alice_subnet_netuid}.{subuid}[/magenta]" + ) - # Assert committing was a success - assert success is True, message - assert bool(re.match(r"reveal_round:\d+", message)) + # commit_block is the block when weights were committed on the chain (transaction block) + expected_commit_block = subtensor.block + 1 + # Commit weights + success, message = subtensor.extrinsics.set_weights( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + subuid=subuid, + uids=weight_uids, + weights=weight_vals, + wait_for_inclusion=True, + wait_for_finalization=True, + block_time=BLOCK_TIME, + period=16, + ) - # Parse expected reveal_round - expected_reveal_round = int(message.split(":")[1]) - logging.console.success( - f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, reveal_round: {expected_reveal_round}" - ) + # Assert committing was a success + assert success is True, message + assert bool(re.match(r"reveal_round:\d+", message)) - # let chain to update - subtensor.wait_for_block(subtensor.block + 1) + # Parse expected reveal_round + expected_reveal_round = int(message.split(":")[1]) + logging.console.success( + f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, reveal_round: {expected_reveal_round}" + ) - # Fetch current commits pending on the chain - commits_on_chain = subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid - ) - address, commit_block, commit, reveal_round = commits_on_chain[0] + # let chain to update + subtensor.wait_for_block(subtensor.block + 1) - # Assert correct values are committed on the chain - assert expected_reveal_round == reveal_round - assert address == alice_wallet.hotkey.ss58_address + # Fetch current commits pending on the chain + commits_on_chain = subtensor.commitments.get_timelocked_weight_commits( + netuid=alice_subnet_netuid, subuid=subuid + ) + address, commit_block, commit, reveal_round = commits_on_chain[0] - # bc of the drand delay, the commit block can be either the previous block or the current block - assert expected_commit_block in [commit_block - 1, commit_block, commit_block + 1] + # Assert correct values are committed on the chain + assert expected_reveal_round == reveal_round + assert address == alice_wallet.hotkey.ss58_address - # Ensure no weights are available as of now - assert subtensor.weights(netuid=alice_subnet_netuid) == [] - logging.console.success("No weights are available before next epoch.") + # bc of the drand delay, the commit block can be either the previous block or the current block + assert expected_commit_block in [ + commit_block - 1, + commit_block, + commit_block + 1, + ] - # 5 is safety drand offset - expected_reveal_block = ( - subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 5 - ) + # Ensure no weights are available as of now + assert subtensor.weights(netuid=alice_subnet_netuid, subuid=subuid) == [] + logging.console.success("No weights are available before next epoch.") - logging.console.info( - f"Waiting for the next epoch to ensure weights are revealed: block {expected_reveal_block}" - ) - subtensor.wait_for_block(expected_reveal_block) + # 5 is safety drand offset + expected_reveal_block = ( + subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 5 + ) - # Fetch the latest drand pulse - latest_drand_round = subtensor.chain.last_drand_round() - logging.console.info( - f"Latest drand round after waiting for tempo: {latest_drand_round}" - ) + logging.console.info( + f"Waiting for the next epoch to ensure weights are revealed: block {expected_reveal_block}" + ) + subtensor.wait_for_block(expected_reveal_block) - # Fetch weights on the chain as they should be revealed now - subnet_weights = subtensor.subnets.weights(netuid=alice_subnet_netuid) - assert subnet_weights != [], "Weights are not available yet." + # Fetch the latest drand pulse + latest_drand_round = 0 - logging.console.info(f"Revealed weights: {subnet_weights}") + while latest_drand_round <= expected_reveal_round: + latest_drand_round = subtensor.chain.last_drand_round() + logging.console.info( + f"Latest drand round: {latest_drand_round}, waiting for next round..." + ) + # drand round is 2 + time.sleep(3) - revealed_weights = subnet_weights[0][1] - # Assert correct weights were revealed - assert weight_uids[0] == revealed_weights[0][0] - assert weight_vals[0] == revealed_weights[0][1] + # Fetch weights on the chain as they should be revealed now + subnet_weights = subtensor.subnets.weights( + netuid=alice_subnet_netuid, subuid=subuid + ) + assert subnet_weights != [], "Weights are not available yet." - logging.console.success( - f"Successfully revealed weights: uids {weight_uids}, weights {weight_vals}" - ) + logging.console.info(f"Revealed weights: {subnet_weights}") - # Now that the commit has been revealed, there shouldn't be any pending commits - assert ( - subtensor.commitments.get_timelocked_weight_commits(netuid=alice_subnet_netuid) - == [] - ) + revealed_weights = subnet_weights[0][1] + # Assert correct weights were revealed + assert weight_uids[0] == revealed_weights[0][0] + assert weight_vals[0] == revealed_weights[0][1] - # Ensure the drand_round is always in the positive w.r.t expected when revealed - assert latest_drand_round - expected_reveal_round >= -3, ( - f"latest_drand_round ({latest_drand_round}) is less than expected_reveal_round ({expected_reveal_round})" - ) + logging.console.success( + f"Successfully revealed weights: uids {weight_uids}, weights {weight_vals}" + ) + + # Now that the commit has been revealed, there shouldn't be any pending commits + assert ( + subtensor.commitments.get_timelocked_weight_commits( + netuid=alice_subnet_netuid, subuid=subuid + ) + == [] + ) + + # Ensure the drand_round is always in the positive w.r.t expected when revealed + assert latest_drand_round - expected_reveal_round >= -3, ( + f"latest_drand_round ({latest_drand_round}) is less than expected_reveal_round ({expected_reveal_round})" + ) logging.console.success("✅ Passed `test_commit_and_reveal_weights_cr4`") @@ -265,133 +291,133 @@ async def test_async_commit_and_reveal_weights_cr4( AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") + # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" + assert await async_sudo_set_admin_freez_window(async_subtensor, alice_wallet, 0) - logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") + # 12 for non-fast-block, 0.25 for fast block + BLOCK_TIME, TEMPO_TO_SET = ( + (0.25, 100) if (await async_subtensor.chain.is_fast_blocks()) else (12.0, 20) + ) - async with async_subtensor: - # 12 for non-fast-block, 0.25 for fast block - BLOCK_TIME, TEMPO_TO_SET = ( - (0.25, 100) if await async_subtensor.chain.is_fast_blocks() else (12.0, 20) - ) + TESTED_SUB_SUBNETS = 3 - logging.console.info(f"Using block time: {BLOCK_TIME}") + logging.console.info(f"Using block time: {BLOCK_TIME}") - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - # Register root as Alice - assert await async_subtensor.extrinsics.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) + # Register root as Alice + assert await async_subtensor.extrinsics.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) - # Verify subnet 2 created successfully - assert await async_subtensor.subnet_exists(alice_subnet_netuid), ( - f"SN #{alice_subnet_netuid} wasn't created successfully" - ) + # Verify subnet 2 created successfully + assert await async_subtensor.subnet_exists(alice_subnet_netuid), ( + f"SN #{alice_subnet_netuid} wasn't created successfully" + ) - logging.console.success(f"SN #{alice_subnet_netuid} is registered.") + assert await async_sudo_set_sub_subnet_count_extrinsic( + async_subtensor, alice_wallet, alice_subnet_netuid, TESTED_SUB_SUBNETS + ), "Cannot create sub-subnets." - # Enable commit_reveal on the subnet - assert sudo_set_hyperparameter_bool( - substrate=local_chain, - wallet=alice_wallet, - call_function="sudo_set_commit_reveal_weights_enabled", - value=True, - netuid=alice_subnet_netuid, - ), f"Unable to enable commit reveal on the SN #{alice_subnet_netuid}" + logging.console.success(f"SN #{alice_subnet_netuid} is registered.") - # Verify commit_reveal was enabled - assert await async_subtensor.subnets.commit_reveal_enabled( - alice_subnet_netuid - ), "Failed to enable commit/reveal" - logging.console.success("Commit reveal enabled") + # Enable commit_reveal on the subnet + assert sudo_set_hyperparameter_bool( + substrate=local_chain, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=True, + netuid=alice_subnet_netuid, + ), f"Unable to enable commit reveal on the SN #{alice_subnet_netuid}" - cr_version = await async_subtensor.substrate.query( - module="SubtensorModule", storage_function="CommitRevealWeightsVersion" - ) - assert cr_version == 4, f"Commit reveal version is not 3, got {cr_version}" + # Verify commit_reveal was enabled + assert await async_subtensor.subnets.commit_reveal_enabled(alice_subnet_netuid), ( + "Failed to enable commit/reveal" + ) + logging.console.success("Commit reveal enabled") - # Change the weights rate limit on the subnet - status, error = sudo_set_admin_utils( - substrate=local_chain, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={"netuid": alice_subnet_netuid, "weights_set_rate_limit": "0"}, - ) + cr_version = await async_subtensor.substrate.query( + module="SubtensorModule", storage_function="CommitRevealWeightsVersion" + ) + assert cr_version == 4, f"Commit reveal version is not 3, got {cr_version}" - assert status is True - assert error is None + # Change the weights rate limit on the subnet + status, error = sudo_set_admin_utils( + substrate=local_chain, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={"netuid": alice_subnet_netuid, "weights_set_rate_limit": "0"}, + ) - # Verify weights rate limit was changed - assert ( - await async_subtensor.subnets.get_subnet_hyperparameters( - netuid=alice_subnet_netuid - ) - ).weights_rate_limit == 0, "Failed to set weights_rate_limit" - assert await async_subtensor.weights_rate_limit(netuid=alice_subnet_netuid) == 0 - logging.console.success("sudo_set_weights_set_rate_limit executed: set to 0") + assert status is True + assert error is None - # Change the tempo of the subnet - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": alice_subnet_netuid, "tempo": TEMPO_TO_SET}, - )[0] - is True + # Verify weights rate limit was changed + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters( + netuid=alice_subnet_netuid ) + ).weights_rate_limit == 0, "Failed to set weights_rate_limit" + assert await async_subtensor.weights_rate_limit(netuid=alice_subnet_netuid) == 0 + logging.console.success("sudo_set_weights_set_rate_limit executed: set to 0") - tempo = ( - await async_subtensor.subnets.get_subnet_hyperparameters( - netuid=alice_subnet_netuid - ) - ).tempo - assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." - logging.console.success( - f"SN #{alice_subnet_netuid} tempo set to {TEMPO_TO_SET}" + # Change the tempo of the subnet + assert ( + sudo_set_admin_utils( + local_chain, + alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": alice_subnet_netuid, "tempo": TEMPO_TO_SET}, + )[0] + is True + ) + + tempo = ( + await async_subtensor.subnets.get_subnet_hyperparameters( + netuid=alice_subnet_netuid ) + ).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + logging.console.success(f"SN #{alice_subnet_netuid} tempo set to {TEMPO_TO_SET}") - # Commit-reveal values - setting weights to self - uids = np.array([0], dtype=np.int64) - weights = np.array([0.1], dtype=np.float32) + # Commit-reveal values - setting weights to self + uids = np.array([0], dtype=np.int64) + weights = np.array([0.1], dtype=np.float32) - weight_uids, weight_vals = convert_weights_and_uids_for_emit( - uids=uids, weights=weights - ) - logging.console.info( - f"Committing weights: uids {weight_uids}, weights {weight_vals}" - ) + weight_uids, weight_vals = convert_weights_and_uids_for_emit( + uids=uids, weights=weights + ) + logging.console.info( + f"Committing weights: uids {weight_uids}, weights {weight_vals}" + ) - # Fetch current block and calculate next tempo for the subnet - current_block = await async_subtensor.chain.get_current_block() - upcoming_tempo = next_tempo(current_block, tempo) - logging.console.info( - f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" + # Fetch current block and calculate next tempo for the subnet + current_block = await async_subtensor.chain.get_current_block() + upcoming_tempo = next_tempo(current_block, tempo) + logging.console.info( + f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" + ) + + # Lower than this might mean weights will get revealed before we can check them + if upcoming_tempo - current_block < 6: + await async_wait_interval( + tempo, + async_subtensor, + netuid=alice_subnet_netuid, + reporting_interval=1, ) + current_block = await async_subtensor.chain.get_current_block() + latest_drand_round = await async_subtensor.chain.last_drand_round() + upcoming_tempo = next_tempo(current_block, tempo) + logging.console.info( + f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" + ) - # Lower than this might mean weights will get revealed before we can check them - if upcoming_tempo - current_block < 6: - await async_wait_interval( - tempo, - async_subtensor, - netuid=alice_subnet_netuid, - reporting_interval=1, - ) - current_block = await async_subtensor.chain.get_current_block() - latest_drand_round = await async_subtensor.chain.last_drand_round() - upcoming_tempo = next_tempo(current_block, tempo) + for subuid in range(TESTED_SUB_SUBNETS): logging.console.info( - f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" + f"[magenta]Testing sub-subnet: {alice_subnet_netuid}.{subuid}[/magenta]" ) # commit_block is the block when weights were committed on the chain (transaction block) @@ -400,6 +426,7 @@ async def test_async_commit_and_reveal_weights_cr4( success, message = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=alice_subnet_netuid, + subuid=subuid, uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, @@ -419,9 +446,11 @@ async def test_async_commit_and_reveal_weights_cr4( ) # Fetch current commits pending on the chain + await async_subtensor.wait_for_block(await async_subtensor.block + subuid) + commits_on_chain = ( await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid + netuid=alice_subnet_netuid, subuid=subuid ) ) address, commit_block, commit, reveal_round = commits_on_chain[0] @@ -434,7 +463,10 @@ async def test_async_commit_and_reveal_weights_cr4( # assert expected_commit_block in [commit_block - 1, commit_block, commit_block + 1] # Ensure no weights are available as of now - assert await async_subtensor.weights(netuid=alice_subnet_netuid) == [] + assert ( + await async_subtensor.weights(netuid=alice_subnet_netuid, subuid=subuid) + == [] + ) logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset @@ -451,14 +483,19 @@ async def test_async_commit_and_reveal_weights_cr4( await async_subtensor.wait_for_block(expected_reveal_block) # Fetch the latest drand pulse - latest_drand_round = await async_subtensor.chain.last_drand_round() - logging.console.info( - f"Latest drand round after waiting for tempo: {latest_drand_round}" - ) + latest_drand_round = 0 + + while latest_drand_round <= expected_reveal_round: + latest_drand_round = await async_subtensor.chain.last_drand_round() + logging.console.info( + f"Latest drand round: {latest_drand_round}, waiting for next round..." + ) + # drand round is 2 + await asyncio.sleep(3) # Fetch weights on the chain as they should be revealed now subnet_weights = await async_subtensor.subnets.weights( - netuid=alice_subnet_netuid + netuid=alice_subnet_netuid, subuid=subuid ) assert subnet_weights != [], "Weights are not available yet." @@ -476,7 +513,7 @@ async def test_async_commit_and_reveal_weights_cr4( # Now that the commit has been revealed, there shouldn't be any pending commits assert ( await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid + netuid=alice_subnet_netuid, subuid=subuid ) == [] ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index d994c2cc4d..6e278fdfa2 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4112,7 +4112,7 @@ async def test_get_stake_weight(subtensor, mocker): async def test_get_timelocked_weight_commits(subtensor, mocker): """Verify that `get_timelocked_weight_commits` method calls proper methods and returns the correct value.""" # Preps - netuid = mocker.Mock() + netuid = 14 mock_determine_block_hash = mocker.patch.object( subtensor, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 8a1f5214cd..c0d081f2b7 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4307,7 +4307,7 @@ def test_get_stake_weight(subtensor, mocker): def test_get_timelocked_weight_commits(subtensor, mocker): """Verify that `get_timelocked_weight_commits` method calls proper methods and returns the correct value.""" # Preps - netuid = mocker.Mock() + netuid = 14 mock_determine_block_hash = mocker.patch.object( subtensor, From 4ac39bcea5fcd825f040fa8b985afc83aa1d8932 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 17:42:14 -0700 Subject: [PATCH 18/39] fix `generate_weight_hash` logic + tests --- bittensor/core/extrinsics/asyncex/sub_subnet.py | 4 ++-- bittensor/core/extrinsics/sub_subnet.py | 4 ++-- bittensor/utils/weight_utils.py | 9 +-------- tests/unit_tests/utils/test_weight_utils.py | 1 - 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/sub_subnet.py b/bittensor/core/extrinsics/asyncex/sub_subnet.py index 1deac2f0b4..1e4875dbfe 100644 --- a/bittensor/core/extrinsics/asyncex/sub_subnet.py +++ b/bittensor/core/extrinsics/asyncex/sub_subnet.py @@ -59,11 +59,11 @@ async def commit_sub_weights_extrinsic( logging.error(unlock.message) return False, unlock.message + storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) # Generate the hash of the weights commit_hash = generate_weight_hash( address=wallet.hotkey.ss58_address, - netuid=netuid, - subuid=subuid, + netuid=storage_index, uids=list(uids), values=list(weights), salt=salt, diff --git a/bittensor/core/extrinsics/sub_subnet.py b/bittensor/core/extrinsics/sub_subnet.py index 607a323af7..c0e514390f 100644 --- a/bittensor/core/extrinsics/sub_subnet.py +++ b/bittensor/core/extrinsics/sub_subnet.py @@ -59,11 +59,11 @@ def commit_sub_weights_extrinsic( logging.error(unlock.message) return False, unlock.message + storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) # Generate the hash of the weights commit_hash = generate_weight_hash( address=wallet.hotkey.ss58_address, - netuid=netuid, - subuid=subuid, + netuid=storage_index, uids=list(uids), values=list(weights), salt=salt, diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index ebca62db22..71c4fdc7c7 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -389,7 +389,6 @@ def process_weights( return non_zero_weight_uids, normalized_weights -# TODO: In SDKv10 move subuid parameter after netuid and remove default value. Add changes to migration guide. def generate_weight_hash( address: str, netuid: int, @@ -397,7 +396,6 @@ def generate_weight_hash( values: list[int], version_key: int, salt: list[int], - subuid: int = 0, ) -> str: """ Generate a valid commit hash from the provided weights. @@ -405,7 +403,6 @@ def generate_weight_hash( Parameters: address: The account identifier. Wallet ss58_address. netuid: The subnet unique identifier. - subuid: The sub-subnet unique identifier. uids: The list of UIDs. salt: The salt to add to hash. values: The list of weight values. @@ -420,10 +417,6 @@ def generate_weight_hash( wallet_address = ScaleBytes(Keypair(ss58_address=address).public_key) netuid = ScaleBytes(netuid.to_bytes(2, "little")) - # If subuid is 0, then subuid is not included in the hash - # for backwards compatibility with old commit hashes on sub-subnets 0 - subuid = b"" if subuid == 0 else ScaleBytes(subuid.to_bytes(2, "little")) - vec_uids = Vec(data=None, sub_type="U16") vec_uids.value = [U16(ScaleBytes(uid.to_bytes(2, "little"))) for uid in uids] uids = ScaleBytes(vec_uids.encode().data) @@ -440,7 +433,7 @@ def generate_weight_hash( vec_salt.value = [U16(ScaleBytes(salts.to_bytes(2, "little"))) for salts in salt] salt = ScaleBytes(vec_salt.encode().data) - data = wallet_address + netuid + subuid + uids + values + salt + version_key + data = wallet_address + netuid + uids + values + salt + version_key # Generate Blake2b hash of the data tuple blake2b_hash = hashlib.blake2b(data.data, digest_size=32) diff --git a/tests/unit_tests/utils/test_weight_utils.py b/tests/unit_tests/utils/test_weight_utils.py index 204c2c7ad4..c378173bfa 100644 --- a/tests/unit_tests/utils/test_weight_utils.py +++ b/tests/unit_tests/utils/test_weight_utils.py @@ -643,7 +643,6 @@ def test_generate_weight_hash(mocker): result = weight_utils.generate_weight_hash( address=fake_address, netuid=fake_netuid, - subuid=0, uids=fake_uids, values=fake_values, version_key=fake_version_key, From b3afc495bed768e073b419c85ad652fb6e36cdca Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 17:42:30 -0700 Subject: [PATCH 19/39] refactoring --- bittensor/core/async_subtensor.py | 26 ++++++++++++++++---------- bittensor/core/subtensor.py | 31 +++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 463969eb62..9fcbd6dbc5 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -900,6 +900,7 @@ async def bonds( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, + subuid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """Retrieves the bond distribution set by subnet validators within a specific subnet. @@ -907,11 +908,12 @@ async def bonds( bonding mechanism is integral to the Yuma Consensus' design intent of incentivizing high-quality performance by subnet miners, and honest evaluation by subnet validators. - Arguments: - netuid: The unique identifier of the subnet. + Parameters: + netuid: Subnet identifier. block: The block number for this query. Do not specify if using block_hash or reuse_block. block_hash: The hash of the block for the query. Do not specify if using reuse_block or block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + subuid: Sub-subnet identifier. Returns: List of tuples mapping each neuron's UID to its bonds with other neurons. @@ -924,11 +926,12 @@ async def bonds( - See - See """ + storage_index = get_sub_subnet_storage_index(netuid, subuid) block_hash = await self.determine_block_hash(block, block_hash, reuse_block) b_map_encoded = await self.substrate.query_map( module="SubtensorModule", storage_function="Bonds", - params=[netuid], + params=[storage_index], block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -1730,6 +1733,7 @@ async def get_all_revealed_commitments( result[hotkey_ss58_address] = commitment_message return result + # TODO: deprecated in SDKv10 async def get_current_weight_commit_info( self, netuid: int, @@ -1770,6 +1774,7 @@ async def get_current_weight_commit_info( commits = result.records[0][1] if result.records else [] return [WeightCommitInfo.from_vec_u8(commit) for commit in commits] + # TODO: deprecated in SDKv10 async def get_current_weight_commit_info_v2( self, netuid: int, @@ -2916,24 +2921,24 @@ async def get_subnet_prices( prices.update({0: Balance.from_tao(1)}) return prices + # TODO: update order in SDKv10 async def get_timelocked_weight_commits( self, netuid: int, - subuid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, + subuid: int = 0, ) -> list[tuple[str, int, str, int]]: """ Retrieves CRv4 weight commit information for a specific subnet. Parameters: netuid: Subnet identifier. - subuid: Sub-subnet identifier. block (Optional[int]): The blockchain block number for the query. Default is ``None``. - block_hash: The hash of the block to retrieve the stake from. Do not specify if using block - or reuse_block + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + subuid: Sub-subnet identifier. Returns: A list of commit details, where each item contains: @@ -4297,13 +4302,14 @@ async def handler(block_data: dict): ) return True + # TODO: update order in SDKv10 async def weights( self, netuid: int, - subuid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, + subuid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -4785,7 +4791,7 @@ async def commit_weights( break except Exception as e: logging.error(f"Error committing weights: {e}") - retries += 1 + retries += 1 return success, message @@ -5114,7 +5120,7 @@ async def reveal_weights( break except Exception as e: logging.error(f"Error revealing weights: {e}") - retries += 1 + retries += 1 return success, message diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index eb10e7c3e7..0d764b88a7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -505,7 +505,10 @@ def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]: return None if not call else (self.get_current_block() - int(call[uid])) def bonds( - self, netuid: int, block: Optional[int] = None + self, + netuid: int, + block: Optional[int] = None, + subuid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the bond distribution set by neurons within a specific subnet of the Bittensor network. @@ -513,9 +516,10 @@ def bonds( and perceived value. This bonding mechanism is integral to the network's market-based approach to measuring and rewarding machine intelligence. - Args: - netuid: The network UID of the subnet to query. + Parameters: + netuid: Subnet identifier. block: the block number for this query. + subuid: Sub-subnet identifier. Returns: List of tuples mapping each neuron's UID to its bonds with other neurons. @@ -524,10 +528,11 @@ def bonds( subnet. It reflects how neurons recognize and invest in each other's intelligence and contributions, supporting diverse and niche systems within the Bittensor ecosystem. """ + storage_index = get_sub_subnet_storage_index(netuid, subuid) b_map_encoded = self.substrate.query_map( module="SubtensorModule", storage_function="Bonds", - params=[netuid], + params=[storage_index], block_hash=self.determine_block_hash(block), ) b_map = [] @@ -1105,6 +1110,7 @@ def get_all_revealed_commitments( result[hotkey_ss58_address] = commitment_message return result + # TODO: deprecated in SDKv10 def get_current_weight_commit_info( self, netuid: int, block: Optional[int] = None ) -> list[tuple[str, str, int]]: @@ -1138,6 +1144,7 @@ def get_current_weight_commit_info( commits = result.records[0][1] if result.records else [] return [WeightCommitInfo.from_vec_u8(commit) for commit in commits] + # TODO: deprecated in SDKv10 def get_current_weight_commit_info_v2( self, netuid: int, block: Optional[int] = None ) -> list[tuple[str, int, str, int]]: @@ -2087,8 +2094,12 @@ def get_subnet_prices( prices.update({0: Balance.from_tao(1)}) return prices + # TODO: update order in SDKv10 def get_timelocked_weight_commits( - self, netuid: int, subuid: int = 0, block: Optional[int] = None + self, + netuid: int, + block: Optional[int] = None, + subuid: int = 0, ) -> list[tuple[str, int, str, int]]: """ Retrieves CRv4 weight commit information for a specific subnet. @@ -3127,8 +3138,12 @@ def handler(block_data: dict): ) return True + # TODO: update order in SDKv10 def weights( - self, netuid: int, subuid: int = 0, block: Optional[int] = None + self, + netuid: int, + block: Optional[int] = None, + subuid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -3580,7 +3595,7 @@ def commit_weights( break except Exception as e: logging.error(f"Error committing weights: {e}") - retries += 1 + retries += 1 return success, message @@ -3910,7 +3925,7 @@ def reveal_weights( break except Exception as e: logging.error(f"Error revealing weights: {e}") - retries += 1 + retries += 1 return success, message From cd69bb6ff5dfacb18a1855763af40b4e4bad9f2a Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 17:43:06 -0700 Subject: [PATCH 20/39] update e2e tests (use subuid) --- tests/e2e_tests/test_commit_reveal.py | 24 ++-- tests/e2e_tests/test_commit_weights.py | 154 +++++++++++++------------ 2 files changed, 89 insertions(+), 89 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index bb736ee48c..623714886c 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -1,9 +1,18 @@ +import asyncio import re import time -import asyncio + import numpy as np import pytest +from bittensor.core.extrinsics.asyncex.sudo import ( + sudo_set_sub_subnet_count_extrinsic as async_sudo_set_sub_subnet_count_extrinsic, + sudo_set_admin_freez_window as async_sudo_set_admin_freez_window, +) +from bittensor.core.extrinsics.sudo import ( + sudo_set_sub_subnet_count_extrinsic, + sudo_set_admin_freez_window, +) from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit from tests.e2e_tests.utils.chain_interactions import ( @@ -13,15 +22,8 @@ wait_interval, next_tempo, ) -from bittensor.core.extrinsics.sudo import ( - sudo_set_sub_subnet_count_extrinsic, - sudo_set_admin_freez_window, -) -from bittensor.core.extrinsics.asyncex.sudo import ( - sudo_set_sub_subnet_count_extrinsic as async_sudo_set_sub_subnet_count_extrinsic, - sudo_set_admin_freez_window as async_sudo_set_admin_freez_window, -) +TESTED_SUB_SUBNETS = 3 # @pytest.mark.parametrize("local_chain", [True], indirect=True) @@ -51,8 +53,6 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle (0.25, 100) if subtensor.chain.is_fast_blocks() else (12.0, 20) ) - TESTED_SUB_SUBNETS = 3 - logging.console.info(f"Using block time: {BLOCK_TIME}") alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 @@ -301,8 +301,6 @@ async def test_async_commit_and_reveal_weights_cr4( (0.25, 100) if (await async_subtensor.chain.is_fast_blocks()) else (12.0, 20) ) - TESTED_SUB_SUBNETS = 3 - logging.console.info(f"Using block time: {BLOCK_TIME}") alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 81f543ff3c..5db2d4db10 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -2,6 +2,11 @@ import pytest import retry +from bittensor.core.extrinsics.sudo import ( + sudo_set_sub_subnet_count_extrinsic, + sudo_set_admin_freez_window, +) +from bittensor.utils import get_sub_subnet_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit from tests.e2e_tests.utils.chain_interactions import ( @@ -11,6 +16,8 @@ wait_epoch, ) +TESTED_SUB_SUBNETS = 3 + @pytest.mark.asyncio async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wallet): @@ -28,18 +35,12 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa """ # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" + assert sudo_set_admin_freez_window(subtensor, alice_wallet, 0), ( + "Failed to set admin freeze window to 0" + ) netuid = subtensor.get_total_subnets() # 2 - set_tempo = 100 if subtensor.is_fast_blocks() else 10 + set_tempo = 50 if subtensor.is_fast_blocks() else 20 print("Testing test_commit_and_reveal_weights") # Register root as Alice @@ -48,6 +49,10 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa # Verify subnet 2 created successfully assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert sudo_set_sub_subnet_count_extrinsic( + subtensor, alice_wallet, netuid, TESTED_SUB_SUBNETS + ), "Cannot create sub-subnets." + # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( local_chain, @@ -94,71 +99,74 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa }, ) - # Commit-reveal values - uids = np.array([0], dtype=np.int64) - weights = np.array([0.1], dtype=np.float32) - salt = [18, 179, 107, 0, 165, 211, 141, 197] - weight_uids, weight_vals = convert_weights_and_uids_for_emit( - uids=uids, weights=weights - ) + for subuid in range(TESTED_SUB_SUBNETS): + logging.console.info(f"[magenta]Testing sub_subnet {netuid}.{subuid}[/magenta]") - # Commit weights - success, message = subtensor.commit_weights( - wallet=alice_wallet, - netuid=netuid, - salt=salt, - uids=weight_uids, - weights=weight_vals, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + # Commit-reveal values + uids = np.array([0], dtype=np.int64) + weights = np.array([0.1], dtype=np.float32) + salt = [18, 179, 107, 0, 165, 211, 141, 197] + weight_uids, weight_vals = convert_weights_and_uids_for_emit( + uids=uids, weights=weights + ) - assert success is True + # Commit weights + success, message = subtensor.commit_weights( + wallet=alice_wallet, + netuid=netuid, + subuid=subuid, + salt=salt, + uids=weight_uids, + weights=weight_vals, + wait_for_inclusion=True, + wait_for_finalization=True, + ) - weight_commits = subtensor.query_module( - module="SubtensorModule", - name="WeightCommits", - params=[netuid, alice_wallet.hotkey.ss58_address], - ) - # Assert that the committed weights are set correctly - assert weight_commits is not None, "Weight commit not found in storage" - commit_hash, commit_block, reveal_block, expire_block = weight_commits[0] - assert commit_block > 0, f"Invalid block number: {commit_block}" + assert success is True - # Query the WeightCommitRevealInterval storage map - assert subtensor.get_subnet_reveal_period_epochs(netuid) > 0, ( - "Invalid RevealPeriodEpochs" - ) + storage_index = get_sub_subnet_storage_index(netuid, subuid) + weight_commits = subtensor.query_module( + module="SubtensorModule", + name="WeightCommits", + params=[storage_index, alice_wallet.hotkey.ss58_address], + ) + # Assert that the committed weights are set correctly + assert weight_commits is not None, "Weight commit not found in storage" + commit_hash, commit_block, reveal_block, expire_block = weight_commits[0] + assert commit_block > 0, f"Invalid block number: {commit_block}" + + # Query the WeightCommitRevealInterval storage map + assert subtensor.get_subnet_reveal_period_epochs(netuid) > 0, ( + "Invalid RevealPeriodEpochs" + ) - # Wait until the reveal block range - await wait_epoch(subtensor, netuid) + # Wait until the reveal block range + await wait_epoch(subtensor, netuid) - # Reveal weights - success, message = subtensor.reveal_weights( - alice_wallet, - netuid, - uids=weight_uids, - weights=weight_vals, - salt=salt, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + # Reveal weights + success, message = subtensor.reveal_weights( + wallet=alice_wallet, + netuid=netuid, + subuid=subuid, + uids=weight_uids, + weights=weight_vals, + salt=salt, + wait_for_inclusion=True, + wait_for_finalization=True, + ) - assert success is True + assert success is True - # Query the Weights storage map - revealed_weights = subtensor.query_module( - module="SubtensorModule", - name="Weights", - params=[netuid, 0], # netuid and uid - ) + revealed_weights = subtensor.weights(netuid, subuid=subuid) - # Assert that the revealed weights are set correctly - assert revealed_weights is not None, "Weight reveal not found in storage" + # Assert that the revealed weights are set correctly + assert revealed_weights is not None, "Weight reveal not found in storage" + + alice_weights = revealed_weights[0][1] + assert weight_vals[0] == alice_weights[0][1], ( + f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights[0][1]}" + ) - assert weight_vals[0] == revealed_weights[0][1], ( - f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights[0][1]}" - ) print("✅ Passed test_commit_and_reveal_weights") @@ -179,17 +187,11 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall """ # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" - - subnet_tempo = 50 if subtensor.is_fast_blocks() else 10 + assert sudo_set_admin_freez_window(subtensor, alice_wallet, 0), ( + "Failed to set admin freeze window to 0" + ) + + subnet_tempo = 50 if subtensor.is_fast_blocks() else 20 netuid = subtensor.get_total_subnets() # 2 # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos From 07fee6bd949b4d54c50e01697d83b4ac4e4d857f Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 15 Sep 2025 20:11:06 -0700 Subject: [PATCH 21/39] add unit test for new extrinsics --- .../extrinsics/asyncex/test_sub_subnet.py | 288 ++++++++++++++++++ .../unit_tests/extrinsics/test_sub_subnet.py | 284 +++++++++++++++++ 2 files changed, 572 insertions(+) create mode 100644 tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py create mode 100644 tests/unit_tests/extrinsics/test_sub_subnet.py diff --git a/tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py b/tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py new file mode 100644 index 0000000000..b00b8b683a --- /dev/null +++ b/tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py @@ -0,0 +1,288 @@ +import pytest +from bittensor.core.extrinsics.asyncex import sub_subnet + + +@pytest.mark.asyncio +async def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_sub_weights_extrinsic` extrinsic.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + salt = [] + + mocked_get_sub_subnet_storage_index = mocker.patch.object( + sub_subnet, "get_sub_subnet_storage_index" + ) + mocked_generate_weight_hash = mocker.patch.object( + sub_subnet, "generate_weight_hash" + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + result = await sub_subnet.commit_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, + ) + + # Asserts + mocked_get_sub_subnet_storage_index.assert_called_once_with( + netuid=netuid, subuid=subuid + ) + mocked_generate_weight_hash.assert_called_once_with( + address=fake_wallet.hotkey.ss58_address, + netuid=mocked_get_sub_subnet_storage_index.return_value, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=sub_subnet.version_as_int, + ) + mocked_compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="commit_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit_hash": mocked_generate_weight_hash.return_value, + }, + ) + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value + + +@pytest.mark.asyncio +async def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_sub_weights_extrinsic` extrinsic.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + block_time = mocker.Mock() + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + sub_subnet, + "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_get_current_block = mocker.patch.object(subtensor, "get_current_block") + mocked_get_subnet_hyperparameters = mocker.patch.object( + subtensor, "get_subnet_hyperparameters" + ) + mocked_get_sub_subnet_storage_index = mocker.patch.object( + sub_subnet, "get_sub_subnet_storage_index" + ) + mocked_get_encrypted_commit = mocker.patch.object( + sub_subnet, + "get_encrypted_commit", + return_value=(mocker.Mock(), mocker.Mock()), + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", + return_value=( + True, + f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", + ), + ) + + # Call + result = await sub_subnet.commit_timelocked_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + block_time=block_time, + ) + + # Asserts + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_get_sub_subnet_storage_index.assert_called_once_with( + netuid=netuid, subuid=subuid + ) + mocked_get_encrypted_commit.assert_called_once_with( + uids=uids, + weights=weights, + subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, + version_key=sub_subnet.version_as_int, + tempo=mocked_get_subnet_hyperparameters.return_value.tempo, + netuid=mocked_get_sub_subnet_storage_index.return_value, + current_block=mocked_get_current_block.return_value, + block_time=block_time, + hotkey=fake_wallet.hotkey.public_key, + ) + mocked_compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="commit_timelocked_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit": mocked_get_encrypted_commit.return_value[0], + "reveal_round": mocked_get_encrypted_commit.return_value[1], + "commit_reveal_version": 4, + }, + ) + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value + + +@pytest.mark.asyncio +async def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `reveal_sub_weights_extrinsic` extrinsic.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + salt = [] + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + sub_subnet, + "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + result = await sub_subnet.reveal_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, + version_key=sub_subnet.version_as_int, + ) + + # Asserts + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="reveal_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "uids": mocked_convert_and_normalize_weights_and_uids.return_value[0], + "values": mocked_convert_and_normalize_weights_and_uids.return_value[0], + "salt": salt, + "version_key": sub_subnet.version_as_int, + }, + ) + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value + + +@pytest.mark.asyncio +async def test_set_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Verify that the `set_sub_weights_extrinsic` function works as expected.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + sub_subnet, + "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", + return_value=( + True, + "", + ), + ) + + # Call + result = await sub_subnet.set_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + version_key=sub_subnet.version_as_int, + ) + + # Asserts + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="set_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "dests": uids, + "weights": weights, + "version_key": sub_subnet.version_as_int, + }, + ) + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_sub_subnet.py b/tests/unit_tests/extrinsics/test_sub_subnet.py new file mode 100644 index 0000000000..a8518c199d --- /dev/null +++ b/tests/unit_tests/extrinsics/test_sub_subnet.py @@ -0,0 +1,284 @@ +import pytest +from bittensor.core.extrinsics import sub_subnet + + +def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_sub_weights_extrinsic` extrinsic.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + salt = [] + + mocked_get_sub_subnet_storage_index = mocker.patch.object( + sub_subnet, "get_sub_subnet_storage_index" + ) + mocked_generate_weight_hash = mocker.patch.object( + sub_subnet, "generate_weight_hash" + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + result = sub_subnet.commit_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, + ) + + # Asserts + mocked_get_sub_subnet_storage_index.assert_called_once_with( + netuid=netuid, subuid=subuid + ) + mocked_generate_weight_hash.assert_called_once_with( + address=fake_wallet.hotkey.ss58_address, + netuid=mocked_get_sub_subnet_storage_index.return_value, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=sub_subnet.version_as_int, + ) + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit_hash": mocked_generate_weight_hash.return_value, + }, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value + + +def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_sub_weights_extrinsic` extrinsic.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + block_time = mocker.Mock() + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + sub_subnet, + "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_get_current_block = mocker.patch.object(subtensor, "get_current_block") + mocked_get_subnet_hyperparameters = mocker.patch.object( + subtensor, "get_subnet_hyperparameters" + ) + mocked_get_sub_subnet_storage_index = mocker.patch.object( + sub_subnet, "get_sub_subnet_storage_index" + ) + mocked_get_encrypted_commit = mocker.patch.object( + sub_subnet, + "get_encrypted_commit", + return_value=(mocker.Mock(), mocker.Mock()), + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", + return_value=( + True, + f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", + ), + ) + + # Call + result = sub_subnet.commit_timelocked_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + block_time=block_time, + ) + + # Asserts + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_get_sub_subnet_storage_index.assert_called_once_with( + netuid=netuid, subuid=subuid + ) + mocked_get_encrypted_commit.assert_called_once_with( + uids=uids, + weights=weights, + subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, + version_key=sub_subnet.version_as_int, + tempo=mocked_get_subnet_hyperparameters.return_value.tempo, + netuid=mocked_get_sub_subnet_storage_index.return_value, + current_block=mocked_get_current_block.return_value, + block_time=block_time, + hotkey=fake_wallet.hotkey.public_key, + ) + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_timelocked_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "commit": mocked_get_encrypted_commit.return_value[0], + "reveal_round": mocked_get_encrypted_commit.return_value[1], + "commit_reveal_version": 4, + }, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value + + +def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `reveal_sub_weights_extrinsic` extrinsic.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + salt = [] + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + sub_subnet, + "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + result = sub_subnet.reveal_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + salt=salt, + version_key=sub_subnet.version_as_int, + ) + + # Asserts + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="reveal_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "uids": mocked_convert_and_normalize_weights_and_uids.return_value[0], + "values": mocked_convert_and_normalize_weights_and_uids.return_value[0], + "salt": salt, + "version_key": sub_subnet.version_as_int, + }, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value + + +def test_set_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Verify that the `set_sub_weights_extrinsic` function works as expected.""" + # Preps + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + subuid = mocker.Mock() + uids = [] + weights = [] + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + sub_subnet, + "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", + return_value=( + True, + "", + ), + ) + + # Call + result = sub_subnet.set_sub_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=netuid, + subuid=subuid, + uids=uids, + weights=weights, + version_key=sub_subnet.version_as_int, + ) + + # Asserts + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="set_sub_weights", + call_params={ + "netuid": netuid, + "subid": subuid, + "dests": uids, + "weights": weights, + "version_key": sub_subnet.version_as_int, + }, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + wallet=fake_wallet, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", + use_nonce=True, + period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert result == mocked_sign_and_send_extrinsic.return_value From 3bb28f47c0d44c49ce7dd246e72acfb586feb5ef Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 16 Sep 2025 17:31:32 -0700 Subject: [PATCH 22/39] update `sudo_set_sub_subnet_emission_split` --- bittensor/core/extrinsics/asyncex/sudo.py | 9 +++-- bittensor/core/extrinsics/sudo.py | 9 +++-- bittensor/utils/weight_utils.py | 40 +++++++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/sudo.py b/bittensor/core/extrinsics/asyncex/sudo.py index 8823d02687..b9837f449f 100644 --- a/bittensor/core/extrinsics/asyncex/sudo.py +++ b/bittensor/core/extrinsics/asyncex/sudo.py @@ -1,6 +1,8 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.extrinsics.asyncex.utils import sudo_call_extrinsic +from bittensor.core.types import Weights as MaybeSplit +from bittensor.utils.weight_utils import convert_maybe_split_to_u16 if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -97,7 +99,7 @@ async def sudo_set_sub_subnet_emission_split( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - maybe_split: list[int], + maybe_split: MaybeSplit, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -130,7 +132,10 @@ async def sudo_set_sub_subnet_emission_split( share[i] = maybe_split[i] / sum(maybe_split) """ call_function = "sudo_set_subsubnet_emission_split" - call_params = {"netuid": netuid, "maybe_split": maybe_split} + call_params = { + "netuid": netuid, + "maybe_split": convert_maybe_split_to_u16(maybe_split), + } return await sudo_call_extrinsic( subtensor=subtensor, wallet=wallet, diff --git a/bittensor/core/extrinsics/sudo.py b/bittensor/core/extrinsics/sudo.py index d734985485..a4758ffd92 100644 --- a/bittensor/core/extrinsics/sudo.py +++ b/bittensor/core/extrinsics/sudo.py @@ -1,6 +1,8 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.extrinsics.utils import sudo_call_extrinsic +from bittensor.core.types import Weights as MaybeSplit +from bittensor.utils.weight_utils import convert_maybe_split_to_u16 if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -97,7 +99,7 @@ def sudo_set_sub_subnet_emission_split( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - maybe_split: list[int], + maybe_split: MaybeSplit, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -130,7 +132,10 @@ def sudo_set_sub_subnet_emission_split( share[i] = maybe_split[i] / sum(maybe_split) """ call_function = "sudo_set_subsubnet_emission_split" - call_params = {"netuid": netuid, "maybe_split": maybe_split} + call_params = { + "netuid": netuid, + "maybe_split": convert_maybe_split_to_u16(maybe_split), + } return sudo_call_extrinsic( subtensor=subtensor, wallet=wallet, diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index 71c4fdc7c7..0c0ecc1368 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -8,6 +8,7 @@ from bittensor_wallet import Keypair from numpy.typing import NDArray from scalecodec import U16, ScaleBytes, Vec +from bittensor.core.types import Weights as MaybeSplit from bittensor.utils.btlogging import logging from bittensor.utils.registration import legacy_torch_api_compat, torch, use_torch @@ -481,3 +482,42 @@ def convert_and_normalize_weights_and_uids( # Reformat and normalize and return return convert_weights_and_uids_for_emit(*convert_uids_and_weights(uids, weights)) + + +def convert_maybe_split_to_u16(maybe_split: MaybeSplit) -> list[int]: + # Convert np.ndarray to list + if isinstance(maybe_split, np.ndarray): + maybe_split = maybe_split.tolist() + + # Ensure we now work with list of numbers + if not isinstance(maybe_split, list) or not maybe_split: + raise ValueError("maybe_split must be a non-empty list or array of numbers.") + + # Ensure all elements are valid numbers + try: + values = [float(x) for x in maybe_split] + except Exception: + raise ValueError("maybe_split must contain numeric values (int or float).") + + if any(x < 0 for x in values): + raise ValueError("maybe_split cannot contain negative values.") + + total = sum(values) + if total <= 0: + raise ValueError("maybe_split must sum to a positive value.") + + # Normalize to sum = 1.0 + normalized = [x / total for x in values] + + # Scale to u16 and round + u16_vals = [round(val * U16_MAX) for val in normalized] + + # Fix rounding error + diff = sum(u16_vals) - U16_MAX + if diff != 0: + max_idx = u16_vals.index(max(u16_vals)) + u16_vals[max_idx] -= diff + + assert sum(u16_vals) == U16_MAX, "Final split must sum to U16_MAX (65535)." + + return u16_vals From 0b204ad21209b392fb9cc039f383263ae8a2b633 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 16 Sep 2025 18:10:14 -0700 Subject: [PATCH 23/39] add `subtensor.get_sub_subnets_emission_split` + tests --- bittensor/core/async_subtensor.py | 31 ++++++++++++++++++++ bittensor/core/subtensor.py | 25 ++++++++++++++++ tests/unit_tests/test_async_subtensor.py | 37 ++++++++++++++++++++++++ tests/unit_tests/test_subtensor.py | 36 +++++++++++++++++++++++ 4 files changed, 129 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 9fcbd6dbc5..e4c4f8fd99 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2706,6 +2706,37 @@ async def get_sub_all_metagraphs( return MetagraphInfo.list_from_dicts(query.value) + async def get_sub_subnets_emission_split( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[list[int]]: + """Returns the emission percentages allocated to each sub-subnet. + + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + A list of integers representing the percentage of emission allocated to each sub-subnet (rounded to whole + numbers). Returns None if emission is evenly split or if the data is unavailable. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + result = await self.substrate.query( + module="SubtensorModule", + storage_function="SubsubnetEmissionSplit", + params=[netuid], + block_hash=block_hash, + ) + if result is None or not hasattr(result, "value"): + return None + + return [round(i / sum(result.value) * 100) for i in result.value] + async def get_sub_metagraph_info( self, netuid: int, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0d764b88a7..d3756e9c1b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1911,6 +1911,31 @@ def get_sub_all_metagraphs( return MetagraphInfo.list_from_dicts(query.value) + def get_sub_subnets_emission_split( + self, netuid: int, block: Optional[int] = None + ) -> Optional[list[int]]: + """Returns the emission percentages allocated to each sub-subnet. + + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + A list of integers representing the percentage of emission allocated to each sub-subnet (rounded to whole + numbers). Returns None if emission is evenly split or if the data is unavailable. + """ + block_hash = self.determine_block_hash(block) + result = self.substrate.query( + module="SubtensorModule", + storage_function="SubsubnetEmissionSplit", + params=[netuid], + block_hash=block_hash, + ) + if result is None or not hasattr(result, "value"): + return None + + return [round(i / sum(result.value) * 100) for i in result.value] + def get_sub_metagraph_info( self, netuid: int, diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 6e278fdfa2..d00ca7a0d1 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4180,6 +4180,43 @@ async def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): assert result == mocked_metagraph_info_list_from_dicts.return_value +@pytest.mark.parametrize( + "query_return, expected_result", + ( + ["value", [10, 90]], + [None, None], + ), +) +@pytest.mark.asyncio +async def test_get_sub_subnets_emission_split( + subtensor, mocker, query_return, expected_result +): + """Verify that get_sub_subnets_emission_split calls the correct methods.""" + # Preps + netuid = mocker.Mock() + query_return = ( + mocker.Mock(value=[6553, 58982]) if query_return == "value" else query_return + ) + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_query = mocker.patch.object( + subtensor.substrate, "query", return_value=query_return + ) + + # Call + + result = await subtensor.get_sub_subnets_emission_split(netuid) + + # Asserts + mocked_determine_block_hash.assert_awaited_once() + mocked_query.assert_awaited_once_with( + module="SubtensorModule", + storage_function="SubsubnetEmissionSplit", + params=[netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result == expected_result + + @pytest.mark.asyncio async def test_get_sub_metagraph_info_returns_none(subtensor, mocker): """Verify that `get_sub_metagraph_info` method returns None.""" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index c0d081f2b7..5edb1265f6 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4371,6 +4371,42 @@ def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): assert result == mocked_metagraph_info_list_from_dicts.return_value +@pytest.mark.parametrize( + "query_return, expected_result", + ( + ["value", [10, 90]], + [None, None], + ), +) +def test_get_sub_subnets_emission_split( + subtensor, mocker, query_return, expected_result +): + """Verify that get_sub_subnets_emission_split calls the correct methods.""" + # Preps + netuid = mocker.Mock() + query_return = ( + mocker.Mock(value=[6553, 58982]) if query_return == "value" else query_return + ) + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_query = mocker.patch.object( + subtensor.substrate, "query", return_value=query_return + ) + + # Call + + result = subtensor.get_sub_subnets_emission_split(netuid) + + # Asserts + mocked_determine_block_hash.assert_called_once() + mocked_query.assert_called_once_with( + module="SubtensorModule", + storage_function="SubsubnetEmissionSplit", + params=[netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + assert result == expected_result + + def test_get_sub_metagraph_info_returns_none(subtensor, mocker): """Verify that `get_sub_metagraph_info` method returns None.""" # Preps From 18a054ba0e07e5754184fe7f162b55b6003972d5 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 16 Sep 2025 18:51:29 -0700 Subject: [PATCH 24/39] fix SubtensorApi tests --- bittensor/core/subtensor_api/subnets.py | 4 ++++ bittensor/core/subtensor_api/utils.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index 5d2793aa14..d812909143 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -25,6 +25,10 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): subtensor.get_neuron_for_pubkey_and_subnet ) self.get_next_epoch_start_block = subtensor.get_next_epoch_start_block + self.get_sub_all_metagraphs = subtensor.get_sub_all_metagraphs + self.get_sub_subnets_emission_split = subtensor.get_sub_subnets_emission_split + self.get_sub_metagraph_info = subtensor.get_sub_metagraph_info + self.get_sub_selective_metagraph = subtensor.get_sub_selective_metagraph self.get_sub_subnet_count = subtensor.get_sub_subnet_count self.get_subnet_burn_cost = subtensor.get_subnet_burn_cost self.get_subnet_hyperparameters = subtensor.get_subnet_hyperparameters diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 6dac1425cf..5c74b4f9c8 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -93,6 +93,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_stake_operations_fee = subtensor._subtensor.get_stake_operations_fee subtensor.get_stake_weight = subtensor._subtensor.get_stake_weight subtensor.get_sub_all_metagraphs = subtensor._subtensor.get_sub_all_metagraphs + subtensor.get_sub_subnets_emission_split = ( + subtensor._subtensor.get_sub_subnets_emission_split + ) subtensor.get_sub_metagraph_info = subtensor._subtensor.get_sub_metagraph_info subtensor.get_sub_selective_metagraph = ( subtensor._subtensor.get_sub_selective_metagraph From 3cfd4933352bd830d104a5628aece0c3a05ca964 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 17 Sep 2025 14:37:27 -0700 Subject: [PATCH 25/39] rename `sub subnets` to `mechanism` --- bittensor/core/async_subtensor.py | 124 +++++++++--------- bittensor/core/chain_data/metagraph_info.py | 8 +- bittensor/core/errors.py | 2 +- bittensor/core/extrinsics/asyncex/children.py | 2 +- .../asyncex/{sub_subnet.py => mechanism.py} | 46 +++---- bittensor/core/extrinsics/asyncex/sudo.py | 32 ++--- bittensor/core/extrinsics/children.py | 2 +- .../{sub_subnet.py => mechanism.py} | 46 +++---- bittensor/core/extrinsics/sudo.py | 30 ++--- bittensor/core/subtensor.py | 122 +++++++++-------- bittensor/core/subtensor_api/metagraphs.py | 6 +- bittensor/core/subtensor_api/subnets.py | 10 +- bittensor/core/subtensor_api/utils.py | 14 +- bittensor/utils/__init__.py | 20 +-- bittensor/utils/easy_imports.py | 4 +- bittensor/utils/weight_utils.py | 2 - tests/e2e_tests/test_commit_reveal.py | 50 +++---- tests/e2e_tests/test_commit_weights.py | 32 ++--- tests/e2e_tests/test_hotkeys.py | 13 +- tests/e2e_tests/test_set_weights.py | 24 ++-- .../extrinsics/asyncex/test_sub_subnet.py | 104 +++++++-------- .../unit_tests/extrinsics/test_sub_subnet.py | 90 +++++++------ tests/unit_tests/test_async_subtensor.py | 100 +++++++------- tests/unit_tests/test_subtensor.py | 118 +++++++++-------- 24 files changed, 505 insertions(+), 496 deletions(-) rename bittensor/core/extrinsics/asyncex/{sub_subnet.py => mechanism.py} (92%) rename bittensor/core/extrinsics/{sub_subnet.py => mechanism.py} (92%) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e4c4f8fd99..3cb8a9e05e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -75,11 +75,11 @@ add_stake_multiple_extrinsic, ) from bittensor.core.extrinsics.asyncex.start_call import start_call_extrinsic -from bittensor.core.extrinsics.asyncex.sub_subnet import ( - commit_sub_weights_extrinsic, - commit_timelocked_sub_weights_extrinsic, - reveal_sub_weights_extrinsic, - set_sub_weights_extrinsic, +from bittensor.core.extrinsics.asyncex.mechanism import ( + commit_mechanism_weights_extrinsic, + commit_timelocked_mechanism_weights_extrinsic, + reveal_mechanism_weights_extrinsic, + set_mechanism_weights_extrinsic, ) from bittensor.core.extrinsics.asyncex.take import ( decrease_take_extrinsic, @@ -108,7 +108,7 @@ u16_normalized_float, u64_normalized_float, get_transfer_fn_params, - get_sub_subnet_storage_index, + get_mechid_storage_index, ) from bittensor.utils import deprecated_message from bittensor.utils.balance import ( @@ -900,7 +900,7 @@ async def bonds( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - subuid: int = 0, + mechid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """Retrieves the bond distribution set by subnet validators within a specific subnet. @@ -913,7 +913,7 @@ async def bonds( block: The block number for this query. Do not specify if using block_hash or reuse_block. block_hash: The hash of the block for the query. Do not specify if using reuse_block or block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - subuid: Sub-subnet identifier. + mechid: Subnet mechanism identifier. Returns: List of tuples mapping each neuron's UID to its bonds with other neurons. @@ -926,7 +926,7 @@ async def bonds( - See - See """ - storage_index = get_sub_subnet_storage_index(netuid, subuid) + storage_index = get_mechid_storage_index(netuid, mechid) block_hash = await self.determine_block_hash(block, block_hash, reuse_block) b_map_encoded = await self.substrate.query_map( module="SubtensorModule", @@ -2678,14 +2678,14 @@ async def get_stake_add_fee( amount=amount, netuid=netuid, block=block ) - async def get_sub_all_metagraphs( + async def get_all_mechagraphs_info( self, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, ) -> Optional[list["MetagraphInfo"]]: """ - Retrieves all sub metagraphs for all sub-subnets. + Retrieves all metagraphs for all subnet mechanisms. Parameters: block: The blockchain block number for the query. @@ -2693,12 +2693,12 @@ async def get_sub_all_metagraphs( reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. Returns: - The list of metagraphs for all subnets with all sub-subnets if found, `None` otherwise. + The list of metagraphs for all subnet mechanisms if found, `None` otherwise. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) query = await self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_all_submetagraphs", + method="get_all_mechagraphs_info", block_hash=block_hash, ) if query is None or query.value is None: @@ -2706,14 +2706,14 @@ async def get_sub_all_metagraphs( return MetagraphInfo.list_from_dicts(query.value) - async def get_sub_subnets_emission_split( + async def get_mechanism_emission_split( self, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, ) -> Optional[list[int]]: - """Returns the emission percentages allocated to each sub-subnet. + """Returns the emission percentages allocated to each subnet mechanism. Parameters: netuid: The unique identifier of the subnet. @@ -2722,13 +2722,13 @@ async def get_sub_subnets_emission_split( reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. Returns: - A list of integers representing the percentage of emission allocated to each sub-subnet (rounded to whole - numbers). Returns None if emission is evenly split or if the data is unavailable. + A list of integers representing the percentage of emission allocated to each subnet mechanism (rounded to + whole numbers). Returns None if emission is evenly split or if the data is unavailable. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) result = await self.substrate.query( module="SubtensorModule", - storage_function="SubsubnetEmissionSplit", + storage_function="MechanismEmissionSplit", params=[netuid], block_hash=block_hash, ) @@ -2737,64 +2737,62 @@ async def get_sub_subnets_emission_split( return [round(i / sum(result.value) * 100) for i in result.value] - async def get_sub_metagraph_info( + async def get_mechagraph_info( self, netuid: int, - subuid: int, + mechid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, ) -> Optional["MetagraphInfo"]: """ - Retrieves metagraph information for the specified sub-subnet (netuid, subuid). + Retrieves metagraph information for the specified subnet mechanism (netuid, mechid). Parameters: netuid: Subnet identifier. - subuid: Sub-subnet identifier. + mechid: Subnet mechanism identifier. block: The blockchain block number for the query. block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. Returns: - A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the given - netuid does not exist. + A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) query = await self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_submetagraph", - params=[netuid, subuid], + method="get_mechagraph", + params=[netuid, mechid], block_hash=block_hash, ) if query is None or query.value is None: - logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") + logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") return None return MetagraphInfo.from_dict(query.value) - async def get_sub_selective_metagraph( + async def get_selective_mechagraph_info( self, netuid: int, - subuid: int, + mechid: int, field_indices: Union[list[SelectiveMetagraphIndex], list[int]], block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, ) -> Optional["MetagraphInfo"]: """ - Retrieves selective metagraph information for the specified sub-subnet (netuid, subuid). + Retrieves selective metagraph information for the specified subnet mechanism (netuid, mechid). Parameters: netuid: Subnet identifier. - subuid: Sub-subnet identifier. + mechid: Subnet mechanism identifier. field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. block: The blockchain block number for the query. block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. Returns: - A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the does not - exist. + A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) indexes = [ @@ -2803,24 +2801,24 @@ async def get_sub_selective_metagraph( ] query = await self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_selective_submetagraph", - params=[netuid, subuid, indexes if 0 in indexes else [0] + indexes], + method="get_selective_mechagraph", + params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) if query is None or query.value is None: - logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") + logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") return None return MetagraphInfo.from_dict(query.value) - async def get_sub_subnet_count( + async def get_mechanism_count( self, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, ) -> int: - """Retrieves the number of sub-subnets for provided subnet. + """Retrieves the number of mechanisms for the given subnet. Parameters: netuid: Subnet identifier. @@ -2829,12 +2827,12 @@ async def get_sub_subnet_count( reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. Returns: - The number of sub-subnets for provided subnet. + The number of mechanisms for the given subnet. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) query = await self.substrate.query( module="SubtensorModule", - storage_function="SubsubnetCountCurrent", + storage_function="MechanismCountCurrent", params=[netuid], block_hash=block_hash, ) @@ -2959,7 +2957,7 @@ async def get_timelocked_weight_commits( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - subuid: int = 0, + mechid: int = 0, ) -> list[tuple[str, int, str, int]]: """ Retrieves CRv4 weight commit information for a specific subnet. @@ -2969,7 +2967,7 @@ async def get_timelocked_weight_commits( block (Optional[int]): The blockchain block number for the query. Default is ``None``. block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. - subuid: Sub-subnet identifier. + mechid: Subnet mechanism identifier. Returns: A list of commit details, where each item contains: @@ -2980,7 +2978,7 @@ async def get_timelocked_weight_commits( The list may be empty if there are no commits found. """ - storage_index = get_sub_subnet_storage_index(netuid, subuid) + storage_index = get_mechid_storage_index(netuid, mechid) block_hash = await self.determine_block_hash( block=block, block_hash=block_hash, reuse_block=reuse_block ) @@ -4340,7 +4338,7 @@ async def weights( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - subuid: int = 0, + mechid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -4349,10 +4347,10 @@ async def weights( Arguments: netuid: The network UID of the subnet to query. - subuid: Sub-subnet identifier. block: Block number for synchronization, or `None` for the latest block. block_hash: The hash of the blockchain block for the query. reuse_block: reuse the last-used blockchain block hash. + mechid: Subnet mechanism identifier. Returns: A list of tuples mapping each neuron's UID to its assigned weights. @@ -4360,7 +4358,7 @@ async def weights( The weight distribution is a key factor in the network's consensus algorithm and the ranking of neurons, influencing their influence and reward allocation within the subnet. """ - storage_index = get_sub_subnet_storage_index(netuid, subuid) + storage_index = get_mechid_storage_index(netuid, mechid) block_hash = await self.determine_block_hash(block, block_hash, reuse_block) # TODO look into seeing if we can speed this up with storage query w_map_encoded = await self.substrate.query_map( @@ -4761,7 +4759,7 @@ async def commit_weights( wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, - subuid: int = 0, + mechid: int = 0, ) -> tuple[bool, str]: """ Commits a hash of the subnet validator's weight vector to the Bittensor blockchain using the provided wallet. @@ -4781,7 +4779,7 @@ async def commit_weights( 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. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. Returns: tuple[bool, str]: @@ -4806,11 +4804,11 @@ async def commit_weights( while retries < max_retries and success is False: try: - success, message = await commit_sub_weights_extrinsic( + success, message = await commit_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, @@ -5097,7 +5095,7 @@ async def reveal_weights( wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = None, - subuid: int = 0, + mechid: int = 0, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -5116,7 +5114,7 @@ async def reveal_weights( 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. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. Returns: tuple[bool, str]: @@ -5134,11 +5132,11 @@ async def reveal_weights( while retries < max_retries and success is False: try: - success, message = await reveal_sub_weights_extrinsic( + success, message = await reveal_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, @@ -5302,7 +5300,7 @@ async def set_children( 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. + MechanismDoesNotExist: 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. @@ -5462,7 +5460,7 @@ async def set_weights( max_retries: int = 5, block_time: float = 12.0, period: Optional[int] = 8, - subuid: int = 0, + mechid: int = 0, commit_reveal_version: int = 4, ): """ @@ -5486,7 +5484,7 @@ async def set_weights( 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. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. commit_reveal_version: The version of the commit-reveal in the chain. Returns: @@ -5522,7 +5520,7 @@ async def _blocks_weight_limit() -> bool: ) if await self.commit_reveal_enabled(netuid=netuid): - # go with `commit_timelocked_sub_weights_extrinsic` extrinsic + # go with `commit_timelocked_mechanism_weights_extrinsic` extrinsic while ( retries < max_retries @@ -5532,11 +5530,11 @@ async def _blocks_weight_limit() -> bool: logging.info( f"Committing weights for subnet #{netuid}. Attempt {retries + 1} of {max_retries}." ) - success, message = await commit_timelocked_sub_weights_extrinsic( + success, message = await commit_timelocked_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, version_key=version_key, @@ -5549,7 +5547,7 @@ async def _blocks_weight_limit() -> bool: retries += 1 return success, message else: - # go with `set_sub_weights_extrinsic` + # go with `set_mechanism_weights_extrinsic` while ( retries < max_retries @@ -5561,11 +5559,11 @@ async def _blocks_weight_limit() -> bool: f"Setting weights for subnet #[blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - success, message = await set_sub_weights_extrinsic( + success, message = await set_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, version_key=version_key, diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index f6f00f5fe5..3e74a408af 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -8,7 +8,7 @@ from bittensor.core.chain_data.subnet_identity import SubnetIdentity from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils import ( - get_netuid_and_subuid_by_storage_index, + get_netuid_and_mechid_by_storage_index, u64_normalized_float as u64tf, u16_normalized_float as u16tf, ) @@ -173,13 +173,13 @@ class MetagraphInfo(InfoBase): commitments: Optional[tuple[tuple[str, str]]] - subuid: int = 0 + mechid: int = 0 @classmethod def _from_dict(cls, decoded: dict) -> "MetagraphInfo": """Returns a MetagraphInfo object from decoded chain data.""" # Subnet index - _netuid, _subuid = get_netuid_and_subuid_by_storage_index(decoded["netuid"]) + _netuid, _mechid = get_netuid_and_mechid_by_storage_index(decoded["netuid"]) # Name and symbol if name := decoded.get("name"): @@ -203,7 +203,7 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": return cls( # Subnet index netuid=_netuid, - subuid=_subuid, + mechid=_mechid, # Name and symbol name=decoded["name"], symbol=decoded["symbol"], diff --git a/bittensor/core/errors.py b/bittensor/core/errors.py index d4b438bd2f..602b79206a 100644 --- a/bittensor/core/errors.py +++ b/bittensor/core/errors.py @@ -159,7 +159,7 @@ class NotDelegateError(StakeError): """ -class SubNetworkDoesNotExist(ChainTransactionError): +class MechanismDoesNotExist(ChainTransactionError): """ The subnet does not exist. """ diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 46853642fe..e99b1dbd85 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -44,7 +44,7 @@ async def set_children_extrinsic( 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. + MechanismDoesNotExist: 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. diff --git a/bittensor/core/extrinsics/asyncex/sub_subnet.py b/bittensor/core/extrinsics/asyncex/mechanism.py similarity index 92% rename from bittensor/core/extrinsics/asyncex/sub_subnet.py rename to bittensor/core/extrinsics/asyncex/mechanism.py index 1e4875dbfe..41702fe5f2 100644 --- a/bittensor/core/extrinsics/asyncex/sub_subnet.py +++ b/bittensor/core/extrinsics/asyncex/mechanism.py @@ -4,7 +4,7 @@ from bittensor.core.settings import version_as_int from bittensor.core.types import Salt, UIDs, Weights -from bittensor.utils import unlock_key, get_sub_subnet_storage_index +from bittensor.utils import unlock_key, get_mechid_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -16,11 +16,11 @@ from bittensor.core.async_subtensor import AsyncSubtensor -async def commit_sub_weights_extrinsic( +async def commit_mechanism_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, salt: Salt, @@ -36,7 +36,7 @@ async def commit_sub_weights_extrinsic( subtensor: AsyncSubtensor instance. wallet: Bittensor Wallet instance. netuid: The subnet unique identifier. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. uids: NumPy array of neuron UIDs for which weights are being committed. weights: NumPy array of weight values corresponding to each UID. salt: list of randomly generated integers as salt to generated weighted hash. @@ -59,7 +59,7 @@ async def commit_sub_weights_extrinsic( logging.error(unlock.message) return False, unlock.message - storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights commit_hash = generate_weight_hash( address=wallet.hotkey.ss58_address, @@ -72,10 +72,10 @@ async def commit_sub_weights_extrinsic( call = await subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="commit_sub_weights", + call_function="commit_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit_hash": commit_hash, }, ) @@ -106,11 +106,11 @@ async def commit_sub_weights_extrinsic( return False, str(error) -async def commit_timelocked_sub_weights_extrinsic( +async def commit_timelocked_mechanism_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, block_time: Union[int, float], @@ -127,7 +127,7 @@ async def commit_timelocked_sub_weights_extrinsic( subtensor: AsyncSubtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. uids: The list of neuron UIDs that the weights are being set for. weights: The corresponding weights to be set for each UID. block_time: The number of seconds for block duration. @@ -160,7 +160,7 @@ async def commit_timelocked_sub_weights_extrinsic( tempo = subnet_hyperparameters.tempo subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period - storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Encrypt `commit_hash` with t-lock and `get reveal_round` commit_for_reveal, reveal_round = get_encrypted_commit( @@ -177,10 +177,10 @@ async def commit_timelocked_sub_weights_extrinsic( call = await subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="commit_timelocked_sub_weights", + call_function="commit_timelocked_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit": commit_for_reveal, "reveal_round": reveal_round, "commit_reveal_version": commit_reveal_version, @@ -213,11 +213,11 @@ async def commit_timelocked_sub_weights_extrinsic( return False, str(error) -async def reveal_sub_weights_extrinsic( +async def reveal_mechanism_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, salt: Salt, @@ -234,7 +234,7 @@ async def reveal_sub_weights_extrinsic( subtensor: AsyncSubtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. uids: List of neuron UIDs for which weights are being revealed. weights: List of weight values corresponding to each UID. salt: List of salt values corresponding to the hash function. @@ -261,10 +261,10 @@ async def reveal_sub_weights_extrinsic( call = await subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="reveal_sub_weights", + call_function="reveal_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "uids": uids, "values": weights, "salt": salt, @@ -298,11 +298,11 @@ async def reveal_sub_weights_extrinsic( return False, str(error) -async def set_sub_weights_extrinsic( +async def set_mechanism_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, version_key: int, @@ -318,7 +318,7 @@ async def set_sub_weights_extrinsic( subtensor: AsyncSubtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. uids: List of neuron UIDs for which weights are being revealed. weights: List of weight values corresponding to each UID. version_key: Version key for compatibility with the network. @@ -345,10 +345,10 @@ async def set_sub_weights_extrinsic( call = await subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="set_sub_weights", + call_function="set_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "dests": uids, "weights": weights, "version_key": version_key, diff --git a/bittensor/core/extrinsics/asyncex/sudo.py b/bittensor/core/extrinsics/asyncex/sudo.py index b9837f449f..61d961d742 100644 --- a/bittensor/core/extrinsics/asyncex/sudo.py +++ b/bittensor/core/extrinsics/asyncex/sudo.py @@ -9,7 +9,7 @@ from bittensor.core.async_subtensor import AsyncSubtensor -async def sudo_set_admin_freez_window( +async def sudo_set_admin_freez_window_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", window: int, @@ -51,24 +51,24 @@ async def sudo_set_admin_freez_window( ) -async def sudo_set_sub_subnet_count_extrinsic( +async def sudo_set_mechanism_count_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - sub_count: int, + mech_count: int, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ - Sets the number of sub-subnets in the subnet. + Sets the number of subnet mechanisms. Parameters: - subtensor: AsyncSubtensor instance. + subtensor: Subtensor instance. wallet: Bittensor Wallet instance. netuid: The subnet unique identifier. - sub_count: The amount of sub-subnets in the subnet to be set. + mech_count: The amount of subnet mechanism to be set. 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. @@ -81,8 +81,8 @@ async def sudo_set_sub_subnet_count_extrinsic( `True` if the extrinsic executed successfully, `False` otherwise. `message` is a string value describing the success or potential error. """ - call_function = "sudo_set_subsubnet_count" - call_params = {"netuid": netuid, "subsub_count": sub_count} + call_function = "sudo_set_mechanism_count" + call_params = {"netuid": netuid, "mechanism_count": mech_count} return await sudo_call_extrinsic( subtensor=subtensor, wallet=wallet, @@ -95,7 +95,7 @@ async def sudo_set_sub_subnet_count_extrinsic( ) -async def sudo_set_sub_subnet_emission_split( +async def sudo_set_mechanism_emission_split_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, @@ -106,13 +106,13 @@ async def sudo_set_sub_subnet_emission_split( wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ - Sets the emission split between sub-subnets in a provided subnet. + Sets the emission split between mechanisms in a provided subnet. Parameters: subtensor: AsyncSubtensor instance. wallet: Bittensor Wallet instance. netuid: The subnet unique identifier. - maybe_split: List of emission weights (positive integers) for each sub-subnet. + maybe_split: List of emission weights (positive integers) for each subnet mechanism. 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. @@ -126,12 +126,12 @@ async def sudo_set_sub_subnet_emission_split( `message` is a string value describing the success or potential error. Note: - The `maybe_split` list defines the relative emission share for each sub-subnet. - Its length must match the number of active sub-subnets in the subnet. For example, [3, 1, 1] distributes - emissions in a 3:1:1 ratio across sub-subnets 0, 1, and 2. Each sub-subnet's emission share is calculated as: - share[i] = maybe_split[i] / sum(maybe_split) + The `maybe_split` list defines the relative emission share for each subnet mechanism. + Its length must match the number of active mechanisms in the subnet or be shorter, but not equal to zero. For + example, [3, 1, 1] distributes emissions in a 3:1:1 ratio across subnet mechanisms 0, 1, and 2. Each mechanism's + emission share is calculated as: share[i] = maybe_split[i] / sum(maybe_split) """ - call_function = "sudo_set_subsubnet_emission_split" + call_function = "sudo_set_mechanism_emission_split" call_params = { "netuid": netuid, "maybe_split": convert_maybe_split_to_u16(maybe_split), diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index dd91fbe97b..0f85b4de53 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -44,7 +44,7 @@ def set_children_extrinsic( 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. + MechanismDoesNotExist: 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. diff --git a/bittensor/core/extrinsics/sub_subnet.py b/bittensor/core/extrinsics/mechanism.py similarity index 92% rename from bittensor/core/extrinsics/sub_subnet.py rename to bittensor/core/extrinsics/mechanism.py index c0e514390f..d8e83b6183 100644 --- a/bittensor/core/extrinsics/sub_subnet.py +++ b/bittensor/core/extrinsics/mechanism.py @@ -4,7 +4,7 @@ from bittensor.core.settings import version_as_int from bittensor.core.types import Salt, UIDs, Weights -from bittensor.utils import unlock_key, get_sub_subnet_storage_index +from bittensor.utils import unlock_key, get_mechid_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -16,11 +16,11 @@ from bittensor.core.subtensor import Subtensor -def commit_sub_weights_extrinsic( +def commit_mechanism_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, salt: Salt, @@ -36,7 +36,7 @@ def commit_sub_weights_extrinsic( subtensor: Subtensor instance. wallet: Bittensor Wallet instance. netuid: The subnet unique identifier. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. uids: NumPy array of neuron UIDs for which weights are being committed. weights: NumPy array of weight values corresponding to each UID. salt: list of randomly generated integers as salt to generated weighted hash. @@ -59,7 +59,7 @@ def commit_sub_weights_extrinsic( logging.error(unlock.message) return False, unlock.message - storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights commit_hash = generate_weight_hash( address=wallet.hotkey.ss58_address, @@ -72,10 +72,10 @@ def commit_sub_weights_extrinsic( call = subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="commit_sub_weights", + call_function="commit_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit_hash": commit_hash, }, ) @@ -106,11 +106,11 @@ def commit_sub_weights_extrinsic( return False, str(error) -def commit_timelocked_sub_weights_extrinsic( +def commit_timelocked_mechanism_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, block_time: Union[int, float], @@ -127,7 +127,7 @@ def commit_timelocked_sub_weights_extrinsic( subtensor: Subtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. - subuid: The sub-subnet unique identifier. + mechid: The sub-subnet unique identifier. uids: The list of neuron UIDs that the weights are being set for. weights: The corresponding weights to be set for each UID. block_time: The number of seconds for block duration. @@ -160,7 +160,7 @@ def commit_timelocked_sub_weights_extrinsic( tempo = subnet_hyperparameters.tempo subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period - storage_index = get_sub_subnet_storage_index(netuid=netuid, subuid=subuid) + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Encrypt `commit_hash` with t-lock and `get reveal_round` commit_for_reveal, reveal_round = get_encrypted_commit( @@ -177,10 +177,10 @@ def commit_timelocked_sub_weights_extrinsic( call = subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="commit_timelocked_sub_weights", + call_function="commit_timelocked_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit": commit_for_reveal, "reveal_round": reveal_round, "commit_reveal_version": commit_reveal_version, @@ -213,11 +213,11 @@ def commit_timelocked_sub_weights_extrinsic( return False, str(error) -def reveal_sub_weights_extrinsic( +def reveal_mechanism_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, salt: Salt, @@ -234,7 +234,7 @@ def reveal_sub_weights_extrinsic( subtensor: Subtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. uids: List of neuron UIDs for which weights are being revealed. weights: List of weight values corresponding to each UID. salt: List of salt values corresponding to the hash function. @@ -261,10 +261,10 @@ def reveal_sub_weights_extrinsic( call = subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="reveal_sub_weights", + call_function="reveal_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "uids": uids, "values": weights, "salt": salt, @@ -298,11 +298,11 @@ def reveal_sub_weights_extrinsic( return False, str(error) -def set_sub_weights_extrinsic( +def set_mechanism_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - subuid: int, + mechid: int, uids: UIDs, weights: Weights, version_key: int, @@ -318,7 +318,7 @@ def set_sub_weights_extrinsic( subtensor: Subtensor instance. wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. uids: List of neuron UIDs for which weights are being revealed. weights: List of weight values corresponding to each UID. version_key: Version key for compatibility with the network. @@ -345,10 +345,10 @@ def set_sub_weights_extrinsic( call = subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="set_sub_weights", + call_function="set_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "dests": uids, "weights": weights, "version_key": version_key, diff --git a/bittensor/core/extrinsics/sudo.py b/bittensor/core/extrinsics/sudo.py index a4758ffd92..473a45bf55 100644 --- a/bittensor/core/extrinsics/sudo.py +++ b/bittensor/core/extrinsics/sudo.py @@ -9,7 +9,7 @@ from bittensor.core.subtensor import Subtensor -def sudo_set_admin_freez_window( +def sudo_set_admin_freez_window_extrinsic( subtensor: "Subtensor", wallet: "Wallet", window: int, @@ -51,24 +51,24 @@ def sudo_set_admin_freez_window( ) -def sudo_set_sub_subnet_count_extrinsic( +def sudo_set_mechanism_count_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - sub_count: int, + mech_count: int, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ - Sets the number of sub-subnets in the subnet. + Sets the number of subnet mechanisms. Parameters: subtensor: Subtensor instance. wallet: Bittensor Wallet instance. netuid: The subnet unique identifier. - sub_count: The amount of sub-subnets in the subnet to be set. + mech_count: The amount of subnet mechanism to be set. 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. @@ -81,8 +81,8 @@ def sudo_set_sub_subnet_count_extrinsic( `True` if the extrinsic executed successfully, `False` otherwise. `message` is a string value describing the success or potential error. """ - call_function = "sudo_set_subsubnet_count" - call_params = {"netuid": netuid, "subsub_count": sub_count} + call_function = "sudo_set_mechanism_count" + call_params = {"netuid": netuid, "mechanism_count": mech_count} return sudo_call_extrinsic( subtensor=subtensor, wallet=wallet, @@ -95,7 +95,7 @@ def sudo_set_sub_subnet_count_extrinsic( ) -def sudo_set_sub_subnet_emission_split( +def sudo_set_mechanism_emission_split_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, @@ -106,13 +106,13 @@ def sudo_set_sub_subnet_emission_split( wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ - Sets the emission split between sub-subnets in a provided subnet. + Sets the emission split between mechanisms in a provided subnet. Parameters: subtensor: Subtensor instance. wallet: Bittensor Wallet instance. netuid: The subnet unique identifier. - maybe_split: List of emission weights (positive integers) for each sub-subnet. + maybe_split: List of emission weights (positive integers) for each subnet mechanism. 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. @@ -126,12 +126,12 @@ def sudo_set_sub_subnet_emission_split( `message` is a string value describing the success or potential error. Note: - The `maybe_split` list defines the relative emission share for each sub-subnet. - Its length must match the number of active sub-subnets in the subnet. For example, [3, 1, 1] distributes - emissions in a 3:1:1 ratio across sub-subnets 0, 1, and 2. Each sub-subnet's emission share is calculated as: - share[i] = maybe_split[i] / sum(maybe_split) + The `maybe_split` list defines the relative emission share for each subnet mechanism. + Its length must match the number of active mechanisms in the subnet or be shorter, but not equal to zero. For + example, [3, 1, 1] distributes emissions in a 3:1:1 ratio across subnet mechanisms 0, 1, and 2. Each mechanism's + emission share is calculated as: share[i] = maybe_split[i] / sum(maybe_split) """ - call_function = "sudo_set_subsubnet_emission_split" + call_function = "sudo_set_mechanism_emission_split" call_params = { "netuid": netuid, "maybe_split": convert_maybe_split_to_u16(maybe_split), diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index d3756e9c1b..6cc16bf2f4 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -73,11 +73,11 @@ add_stake_multiple_extrinsic, ) from bittensor.core.extrinsics.start_call import start_call_extrinsic -from bittensor.core.extrinsics.sub_subnet import ( - commit_sub_weights_extrinsic, - commit_timelocked_sub_weights_extrinsic, - reveal_sub_weights_extrinsic, - set_sub_weights_extrinsic, +from bittensor.core.extrinsics.mechanism import ( + commit_mechanism_weights_extrinsic, + commit_timelocked_mechanism_weights_extrinsic, + reveal_mechanism_weights_extrinsic, + set_mechanism_weights_extrinsic, ) from bittensor.core.extrinsics.take import ( decrease_take_extrinsic, @@ -111,7 +111,7 @@ u64_normalized_float, deprecated_message, get_transfer_fn_params, - get_sub_subnet_storage_index, + get_mechid_storage_index, ) from bittensor.utils.balance import ( Balance, @@ -508,7 +508,7 @@ def bonds( self, netuid: int, block: Optional[int] = None, - subuid: int = 0, + mechid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the bond distribution set by neurons within a specific subnet of the Bittensor network. @@ -519,7 +519,7 @@ def bonds( Parameters: netuid: Subnet identifier. block: the block number for this query. - subuid: Sub-subnet identifier. + mechid: Subnet mechanism identifier. Returns: List of tuples mapping each neuron's UID to its bonds with other neurons. @@ -528,7 +528,7 @@ def bonds( subnet. It reflects how neurons recognize and invest in each other's intelligence and contributions, supporting diverse and niche systems within the Bittensor ecosystem. """ - storage_index = get_sub_subnet_storage_index(netuid, subuid) + storage_index = get_mechid_storage_index(netuid, mechid) b_map_encoded = self.substrate.query_map( module="SubtensorModule", storage_function="Bonds", @@ -1887,23 +1887,23 @@ def get_stake_add_fee( """ return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) - def get_sub_all_metagraphs( + def get_all_mechagraphs_info( self, block: Optional[int] = None, ) -> Optional[list["MetagraphInfo"]]: """ - Retrieves all sub metagraphs for all sub-subnets. + Retrieves all metagraphs for all subnet mechanisms. Parameters: block: The blockchain block number for the query. Returns: - The list of metagraphs for all subnets with all sub-subnets if found, `None` otherwise. + The list of metagraphs for all subnet mechanisms if found, `None` otherwise. """ block_hash = self.determine_block_hash(block) query = self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_all_submetagraphs", + method="get_all_mechagraphs_info", block_hash=block_hash, ) if query is None or query.value is None: @@ -1911,23 +1911,23 @@ def get_sub_all_metagraphs( return MetagraphInfo.list_from_dicts(query.value) - def get_sub_subnets_emission_split( + def get_mechanism_emission_split( self, netuid: int, block: Optional[int] = None ) -> Optional[list[int]]: - """Returns the emission percentages allocated to each sub-subnet. + """Returns the emission percentages allocated to each subnet mechanism. Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. Returns: - A list of integers representing the percentage of emission allocated to each sub-subnet (rounded to whole - numbers). Returns None if emission is evenly split or if the data is unavailable. + A list of integers representing the percentage of emission allocated to each subnet mechanism (rounded to + whole numbers). Returns None if emission is evenly split or if the data is unavailable. """ block_hash = self.determine_block_hash(block) result = self.substrate.query( module="SubtensorModule", - storage_function="SubsubnetEmissionSplit", + storage_function="MechanismEmissionSplit", params=[netuid], block_hash=block_hash, ) @@ -1936,57 +1936,55 @@ def get_sub_subnets_emission_split( return [round(i / sum(result.value) * 100) for i in result.value] - def get_sub_metagraph_info( + def get_mechagraph_info( self, netuid: int, - subuid: int, + mechid: int, block: Optional[int] = None, ) -> Optional["MetagraphInfo"]: """ - Retrieves metagraph information for the specified sub-subnet (netuid, subuid). + Retrieves metagraph information for the specified subnet mechanism (netuid, mechid). Parameters: netuid: Subnet identifier. - subuid: Sub-subnet identifier. + mechid: Subnet mechanism identifier. block: The blockchain block number for the query. Returns: - A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the given - netuid does not exist. + A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. """ block_hash = self.determine_block_hash(block) query = self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_submetagraph", - params=[netuid, subuid], + method="get_mechagraph", + params=[netuid, mechid], block_hash=block_hash, ) if query is None or query.value is None: - logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") + logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") return None return MetagraphInfo.from_dict(query.value) - def get_sub_selective_metagraph( + def get_selective_mechagraph_info( self, netuid: int, - subuid: int, + mechid: int, field_indices: Union[list[SelectiveMetagraphIndex], list[int]], block: Optional[int] = None, ) -> Optional["MetagraphInfo"]: """ - Retrieves selective metagraph information for the specified sub-subnet (netuid, subuid). + Retrieves selective metagraph information for the specified subnet mechanism (netuid, mechid). Parameters: netuid: Subnet identifier. - subuid: Sub-subnet identifier. + mechid: Subnet mechanism identifier. field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. block: The blockchain block number for the query. Returns: - A MetagraphInfo object containing the requested sub-subnet data, or None if the sub-subnet with the does not - exist. + A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. """ block_hash = self.determine_block_hash(block=block) indexes = [ @@ -1995,34 +1993,34 @@ def get_sub_selective_metagraph( ] query = self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_selective_submetagraph", - params=[netuid, subuid, indexes if 0 in indexes else [0] + indexes], + method="get_selective_mechagraph", + params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) if query is None or query.value is None: - logging.error(f"Sub-subnet {netuid}.{subuid} does not exist.") + logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") return None return MetagraphInfo.from_dict(query.value) - def get_sub_subnet_count( + def get_mechanism_count( self, netuid: int, block: Optional[int] = None, ) -> int: - """Retrieves the number of sub-subnets for provided subnet. + """Retrieves the number of mechanisms for the given subnet. Parameters: netuid: Subnet identifier. block: The blockchain block number for the query. Returns: - The number of sub-subnets for provided subnet. + The number of mechanisms for the given subnet. """ block_hash = self.determine_block_hash(block) query = self.substrate.query( module="SubtensorModule", - storage_function="SubsubnetCountCurrent", + storage_function="MechanismCountCurrent", params=[netuid], block_hash=block_hash, ) @@ -2124,15 +2122,15 @@ def get_timelocked_weight_commits( self, netuid: int, block: Optional[int] = None, - subuid: int = 0, + mechid: int = 0, ) -> list[tuple[str, int, str, int]]: """ Retrieves CRv4 weight commit information for a specific subnet. Parameters: netuid: Subnet identifier. - subuid: Sub-subnet identifier. block: The blockchain block number for the query. Default is ``None``. + mechid: Subnet mechanism identifier. Returns: A list of commit details, where each item contains: @@ -2143,7 +2141,7 @@ def get_timelocked_weight_commits( The list may be empty if there are no commits found. """ - storage_index = get_sub_subnet_storage_index(netuid, subuid) + storage_index = get_mechid_storage_index(netuid, mechid) result = self.substrate.query_map( module="SubtensorModule", storage_function="TimelockedWeightCommits", @@ -3168,7 +3166,7 @@ def weights( self, netuid: int, block: Optional[int] = None, - subuid: int = 0, + mechid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -3177,8 +3175,8 @@ def weights( Arguments: netuid (int): The network UID of the subnet to query. - subuid: Sub-subnet identifier. block (Optional[int]): Block number for synchronization, or ``None`` for the latest block. + mechid: Subnet mechanism identifier. Returns: A list of tuples mapping each neuron's UID to its assigned weights. @@ -3186,7 +3184,7 @@ def weights( The weight distribution is a key factor in the network's consensus algorithm and the ranking of neurons, influencing their influence and reward allocation within the subnet. """ - storage_index = get_sub_subnet_storage_index(netuid, subuid) + storage_index = get_mechid_storage_index(netuid, mechid) w_map_encoded = self.substrate.query_map( module="SubtensorModule", storage_function="Weights", @@ -3563,7 +3561,7 @@ def commit_weights( wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, - subuid: int = 0, + mechid: int = 0, ) -> tuple[bool, str]: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. @@ -3582,7 +3580,7 @@ def commit_weights( 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. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. Returns: tuple[bool, str]: @@ -3604,11 +3602,11 @@ def commit_weights( while retries < max_retries and success is False: try: - success, message = commit_sub_weights_extrinsic( + success, message = commit_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, @@ -3896,7 +3894,7 @@ def reveal_weights( wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, - subuid: int = 0, + mechid: int = 0, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -3915,7 +3913,7 @@ def reveal_weights( 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. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. Returns: tuple[bool, str]: @@ -3933,11 +3931,11 @@ def reveal_weights( while retries < max_retries and success is False: try: - success, message = reveal_sub_weights_extrinsic( + success, message = reveal_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, @@ -4246,7 +4244,7 @@ def set_weights( max_retries: int = 5, block_time: float = 12.0, period: Optional[int] = 8, - subuid: int = 0, + mechid: int = 0, commit_reveal_version: int = 4, ) -> tuple[bool, str]: """ @@ -4267,7 +4265,7 @@ def set_weights( 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. - subuid: The sub-subnet unique identifier. + mechid: The subnet mechanism unique identifier. commit_reveal_version: The version of the commit-reveal in the chain. Returns: @@ -4299,18 +4297,18 @@ def _blocks_weight_limit() -> bool: ) if self.commit_reveal_enabled(netuid=netuid): - # go with `commit_timelocked_sub_weights_extrinsic` extrinsic + # go with `commit_timelocked_mechanism_weights_extrinsic` extrinsic while retries < max_retries and success is False and _blocks_weight_limit(): logging.info( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - success, message = commit_timelocked_sub_weights_extrinsic( + success, message = commit_timelocked_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, version_key=version_key, @@ -4323,7 +4321,7 @@ def _blocks_weight_limit() -> bool: retries += 1 return success, message else: - # go with `set_sub_weights_extrinsic` + # go with `set_mechanism_weights_extrinsic` while retries < max_retries and success is False and _blocks_weight_limit(): try: @@ -4331,11 +4329,11 @@ def _blocks_weight_limit() -> bool: f"Setting weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - success, message = set_sub_weights_extrinsic( + success, message = set_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, version_key=version_key, diff --git a/bittensor/core/subtensor_api/metagraphs.py b/bittensor/core/subtensor_api/metagraphs.py index aa7b2fe2ee..481395336f 100644 --- a/bittensor/core/subtensor_api/metagraphs.py +++ b/bittensor/core/subtensor_api/metagraphs.py @@ -9,7 +9,7 @@ class Metagraphs: def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_metagraph_info = subtensor.get_metagraph_info self.get_all_metagraphs_info = subtensor.get_all_metagraphs_info - self.get_sub_all_metagraphs = subtensor.get_sub_all_metagraphs - self.get_sub_metagraph_info = subtensor.get_sub_metagraph_info - self.get_sub_selective_metagraph = subtensor.get_sub_selective_metagraph + self.get_all_mechagraphs_info = subtensor.get_all_mechagraphs_info + self.get_mechagraph_info = subtensor.get_mechagraph_info + self.get_selective_mechagraph_info = subtensor.get_selective_mechagraph_info self.metagraph = subtensor.metagraph diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index d812909143..6ea5dcd406 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -25,11 +25,11 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): subtensor.get_neuron_for_pubkey_and_subnet ) self.get_next_epoch_start_block = subtensor.get_next_epoch_start_block - self.get_sub_all_metagraphs = subtensor.get_sub_all_metagraphs - self.get_sub_subnets_emission_split = subtensor.get_sub_subnets_emission_split - self.get_sub_metagraph_info = subtensor.get_sub_metagraph_info - self.get_sub_selective_metagraph = subtensor.get_sub_selective_metagraph - self.get_sub_subnet_count = subtensor.get_sub_subnet_count + self.get_all_mechagraphs = subtensor.get_all_mechagraphs_info + self.get_mechanism_emission_split = subtensor.get_mechanism_emission_split + self.get_mechagraph_info = subtensor.get_mechagraph_info + self.get_selective_mechagraph_info = subtensor.get_selective_mechagraph_info + self.get_mechanism_count = subtensor.get_mechanism_count self.get_subnet_burn_cost = subtensor.get_subnet_burn_cost self.get_subnet_hyperparameters = subtensor.get_subnet_hyperparameters self.get_subnet_info = subtensor.get_subnet_info diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 5c74b4f9c8..e57a2095b6 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -92,15 +92,15 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_stake_movement_fee = subtensor._subtensor.get_stake_movement_fee subtensor.get_stake_operations_fee = subtensor._subtensor.get_stake_operations_fee subtensor.get_stake_weight = subtensor._subtensor.get_stake_weight - subtensor.get_sub_all_metagraphs = subtensor._subtensor.get_sub_all_metagraphs - subtensor.get_sub_subnets_emission_split = ( - subtensor._subtensor.get_sub_subnets_emission_split + subtensor.get_all_mechagraphs_info = subtensor._subtensor.get_all_mechagraphs_info + subtensor.get_mechanism_emission_split = ( + subtensor._subtensor.get_mechanism_emission_split ) - subtensor.get_sub_metagraph_info = subtensor._subtensor.get_sub_metagraph_info - subtensor.get_sub_selective_metagraph = ( - subtensor._subtensor.get_sub_selective_metagraph + subtensor.get_mechagraph_info = subtensor._subtensor.get_mechagraph_info + subtensor.get_selective_mechagraph_info = ( + subtensor._subtensor.get_selective_mechagraph_info ) - subtensor.get_sub_subnet_count = subtensor._subtensor.get_sub_subnet_count + subtensor.get_mechanism_count = subtensor._subtensor.get_mechanism_count subtensor.get_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( subtensor._subtensor.get_subnet_hyperparameters diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 2e2c2edeb5..adc002ed4f 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -43,32 +43,32 @@ hex_to_bytes = hex_to_bytes -def get_sub_subnet_storage_index(netuid: int, subuid: int) -> int: - """Computes the storage index for a given netuid and subuid pair. +def get_mechid_storage_index(netuid: int, mechid: int) -> int: + """Computes the storage index for a given netuid and mechid pair. Parameters: netuid: The netuid of the subnet. - subuid: The subuid of the subnet. + mechid: The mechid of the subnet. Returns: - Storage index number for the subnet and subuid. + Storage index number for the subnet and mechanism id. """ - return subuid * GLOBAL_MAX_SUBNET_COUNT + netuid + return mechid * GLOBAL_MAX_SUBNET_COUNT + netuid -def get_netuid_and_subuid_by_storage_index(storage_index: int) -> tuple[int, int]: - """Returns the netuid and subuid from the storage index. +def get_netuid_and_mechid_by_storage_index(storage_index: int) -> tuple[int, int]: + """Returns the netuid and mechid from the storage index. Chain APIs (e.g., SubMetagraph response) returns netuid which is storage index that encodes both the netuid and - subuid. This function reverses the encoding to extract these components. + mechid. This function reverses the encoding to extract these components. Parameters: storage_index: The storage index of the subnet. Returns: tuple[int, int]: - - netuid subnet identifier. - - subuid identifier. + - netuid - subnet identifier. + - mechid - mechanism identifier. """ return ( storage_index % GLOBAL_MAX_SUBNET_COUNT, diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index a2645b0668..327955d68b 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -86,7 +86,7 @@ RegistrationNotPermittedOnRootSubnet, RunException, StakeError, - SubNetworkDoesNotExist, + MechanismDoesNotExist, SynapseDendriteNoneException, SynapseParsingError, TooManyChildren, @@ -253,7 +253,7 @@ def info(on: bool = True): "RegistrationNotPermittedOnRootSubnet", "RunException", "StakeError", - "SubNetworkDoesNotExist", + "MechanismDoesNotExist", "SynapseDendriteNoneException", "SynapseParsingError", "TooManyChildren", diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index 0c0ecc1368..92c798a12d 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -411,8 +411,6 @@ def generate_weight_hash( Returns: str: The generated commit hash. - - Use `subuid=0` if subnet doesn't have sub-subnets. Actually, means subnet has only one sub-subnet with index 0. """ # Encode data using SCALE codec wallet_address = ScaleBytes(Keypair(ss58_address=address).public_key) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 623714886c..a45376e1f6 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -6,12 +6,12 @@ import pytest from bittensor.core.extrinsics.asyncex.sudo import ( - sudo_set_sub_subnet_count_extrinsic as async_sudo_set_sub_subnet_count_extrinsic, - sudo_set_admin_freez_window as async_sudo_set_admin_freez_window, + sudo_set_mechanism_count_extrinsic as async_sudo_set_mechanism_count_extrinsic, + sudo_set_admin_freez_window_extrinsic as async_sudo_set_admin_freez_window_extrinsic, ) from bittensor.core.extrinsics.sudo import ( - sudo_set_sub_subnet_count_extrinsic, - sudo_set_admin_freez_window, + sudo_set_mechanism_count_extrinsic, + sudo_set_admin_freez_window_extrinsic, ) from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit @@ -23,7 +23,7 @@ next_tempo, ) -TESTED_SUB_SUBNETS = 3 +TESTED_SUB_SUBNETS = 2 # @pytest.mark.parametrize("local_chain", [True], indirect=True) @@ -46,7 +46,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window(subtensor, alice_wallet, 0) + assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0) # 12 for non-fast-block, 0.25 for fast block BLOCK_TIME, TEMPO_TO_SET = ( @@ -67,7 +67,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle f"SN #{alice_subnet_netuid} wasn't created successfully" ) - assert sudo_set_sub_subnet_count_extrinsic( + assert sudo_set_mechanism_count_extrinsic( subtensor, alice_wallet, alice_subnet_netuid, TESTED_SUB_SUBNETS ), "Cannot create sub-subnets." @@ -164,9 +164,9 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" ) - for subuid in range(TESTED_SUB_SUBNETS): + for mechid in range(TESTED_SUB_SUBNETS): logging.console.info( - f"[magenta]Testing sub-subnet: {alice_subnet_netuid}.{subuid}[/magenta]" + f"[magenta]Testing subnet mechanism: {alice_subnet_netuid}.{mechid}[/magenta]" ) # commit_block is the block when weights were committed on the chain (transaction block) @@ -175,7 +175,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle success, message = subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=alice_subnet_netuid, - subuid=subuid, + mechid=mechid, uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, @@ -199,7 +199,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Fetch current commits pending on the chain commits_on_chain = subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, subuid=subuid + netuid=alice_subnet_netuid, mechid=mechid ) address, commit_block, commit, reveal_round = commits_on_chain[0] @@ -215,7 +215,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle ] # Ensure no weights are available as of now - assert subtensor.weights(netuid=alice_subnet_netuid, subuid=subuid) == [] + assert subtensor.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset @@ -241,7 +241,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Fetch weights on the chain as they should be revealed now subnet_weights = subtensor.subnets.weights( - netuid=alice_subnet_netuid, subuid=subuid + netuid=alice_subnet_netuid, mechid=mechid ) assert subnet_weights != [], "Weights are not available yet." @@ -259,7 +259,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Now that the commit has been revealed, there shouldn't be any pending commits assert ( subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, subuid=subuid + netuid=alice_subnet_netuid, mechid=mechid ) == [] ) @@ -294,7 +294,9 @@ async def test_async_commit_and_reveal_weights_cr4( logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") # turn off admin freeze window limit for testing - assert await async_sudo_set_admin_freez_window(async_subtensor, alice_wallet, 0) + assert await async_sudo_set_admin_freez_window_extrinsic( + async_subtensor, alice_wallet, 0 + ) # 12 for non-fast-block, 0.25 for fast block BLOCK_TIME, TEMPO_TO_SET = ( @@ -315,7 +317,7 @@ async def test_async_commit_and_reveal_weights_cr4( f"SN #{alice_subnet_netuid} wasn't created successfully" ) - assert await async_sudo_set_sub_subnet_count_extrinsic( + assert await async_sudo_set_mechanism_count_extrinsic( async_subtensor, alice_wallet, alice_subnet_netuid, TESTED_SUB_SUBNETS ), "Cannot create sub-subnets." @@ -413,9 +415,9 @@ async def test_async_commit_and_reveal_weights_cr4( f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" ) - for subuid in range(TESTED_SUB_SUBNETS): + for mechid in range(TESTED_SUB_SUBNETS): logging.console.info( - f"[magenta]Testing sub-subnet: {alice_subnet_netuid}.{subuid}[/magenta]" + f"[magenta]Testing subnet mechanism: {alice_subnet_netuid}.{mechid}[/magenta]" ) # commit_block is the block when weights were committed on the chain (transaction block) @@ -424,7 +426,7 @@ async def test_async_commit_and_reveal_weights_cr4( success, message = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=alice_subnet_netuid, - subuid=subuid, + mechid=mechid, uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, @@ -444,11 +446,11 @@ async def test_async_commit_and_reveal_weights_cr4( ) # Fetch current commits pending on the chain - await async_subtensor.wait_for_block(await async_subtensor.block + subuid) + await async_subtensor.wait_for_block(await async_subtensor.block + 12) commits_on_chain = ( await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, subuid=subuid + netuid=alice_subnet_netuid, mechid=mechid ) ) address, commit_block, commit, reveal_round = commits_on_chain[0] @@ -462,7 +464,7 @@ async def test_async_commit_and_reveal_weights_cr4( # Ensure no weights are available as of now assert ( - await async_subtensor.weights(netuid=alice_subnet_netuid, subuid=subuid) + await async_subtensor.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] ) logging.console.success("No weights are available before next epoch.") @@ -493,7 +495,7 @@ async def test_async_commit_and_reveal_weights_cr4( # Fetch weights on the chain as they should be revealed now subnet_weights = await async_subtensor.subnets.weights( - netuid=alice_subnet_netuid, subuid=subuid + netuid=alice_subnet_netuid, mechid=mechid ) assert subnet_weights != [], "Weights are not available yet." @@ -511,7 +513,7 @@ async def test_async_commit_and_reveal_weights_cr4( # Now that the commit has been revealed, there shouldn't be any pending commits assert ( await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, subuid=subuid + netuid=alice_subnet_netuid, mechid=mechid ) == [] ) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 5db2d4db10..c3826febf1 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -3,10 +3,10 @@ import retry from bittensor.core.extrinsics.sudo import ( - sudo_set_sub_subnet_count_extrinsic, - sudo_set_admin_freez_window, + sudo_set_mechanism_count_extrinsic, + sudo_set_admin_freez_window_extrinsic, ) -from bittensor.utils import get_sub_subnet_storage_index +from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit from tests.e2e_tests.utils.chain_interactions import ( @@ -16,7 +16,7 @@ wait_epoch, ) -TESTED_SUB_SUBNETS = 3 +TESTED_SUB_SUBNETS = 2 @pytest.mark.asyncio @@ -35,7 +35,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa """ # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window(subtensor, alice_wallet, 0), ( + assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" ) @@ -49,7 +49,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa # Verify subnet 2 created successfully assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" - assert sudo_set_sub_subnet_count_extrinsic( + assert sudo_set_mechanism_count_extrinsic( subtensor, alice_wallet, netuid, TESTED_SUB_SUBNETS ), "Cannot create sub-subnets." @@ -99,8 +99,10 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa }, ) - for subuid in range(TESTED_SUB_SUBNETS): - logging.console.info(f"[magenta]Testing sub_subnet {netuid}.{subuid}[/magenta]") + for mechid in range(TESTED_SUB_SUBNETS): + logging.console.info( + f"[magenta]Testing subnet mechanism {netuid}.{mechid}[/magenta]" + ) # Commit-reveal values uids = np.array([0], dtype=np.int64) @@ -114,7 +116,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa success, message = subtensor.commit_weights( wallet=alice_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, salt=salt, uids=weight_uids, weights=weight_vals, @@ -122,9 +124,9 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa wait_for_finalization=True, ) - assert success is True + assert success is True, message - storage_index = get_sub_subnet_storage_index(netuid, subuid) + storage_index = get_mechid_storage_index(netuid, mechid) weight_commits = subtensor.query_module( module="SubtensorModule", name="WeightCommits", @@ -147,7 +149,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa success, message = subtensor.reveal_weights( wallet=alice_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=weight_uids, weights=weight_vals, salt=salt, @@ -155,9 +157,9 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa wait_for_finalization=True, ) - assert success is True + assert success is True, message - revealed_weights = subtensor.weights(netuid, subuid=subuid) + revealed_weights = subtensor.weights(netuid, mechid=mechid) # Assert that the revealed weights are set correctly assert revealed_weights is not None, "Weight reveal not found in storage" @@ -187,7 +189,7 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall """ # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window(subtensor, alice_wallet, 0), ( + assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" ) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 479c5f7d08..d17929547e 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -3,7 +3,7 @@ from bittensor.core.errors import ( NotEnoughStakeToSetChildkeys, RegistrationNotPermittedOnRootSubnet, - SubNetworkDoesNotExist, + MechanismDoesNotExist, InvalidChild, TooManyChildren, ProportionOverflow, @@ -11,11 +11,13 @@ TxRateLimitExceeded, NonAssociatedColdKey, ) +from bittensor.core.extrinsics.sudo import ( + sudo_set_admin_freez_window_extrinsic, +) from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import sudo_set_admin_utils from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call - SET_CHILDREN_RATE_LIMIT = 15 ROOT_COOLDOWN = 15 # blocks @@ -86,6 +88,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w - Clear children list """ + # turn off admin freeze window limit for testing + assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0) + dave_subnet_netuid = subtensor.get_total_subnets() # 2 set_tempo = 10 # affect to non-fast-blocks mode @@ -146,7 +151,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w raise_error=True, ) - with pytest.raises(SubNetworkDoesNotExist): + with pytest.raises(MechanismDoesNotExist): subtensor.set_children( alice_wallet, alice_wallet.hotkey.ss58_address, @@ -336,7 +341,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w assert pending == [] - subtensor.wait_for_block(cooldown) + subtensor.wait_for_block(cooldown + 1) success, children, error = subtensor.get_children( hotkey=alice_wallet.hotkey.ss58_address, diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 0e8e3a95c6..29a9a1c2fc 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -3,8 +3,8 @@ import retry from bittensor.core.extrinsics.sudo import ( - sudo_set_sub_subnet_count_extrinsic, - sudo_set_admin_freez_window, + sudo_set_mechanism_count_extrinsic, + sudo_set_admin_freez_window_extrinsic, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -32,7 +32,7 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) AssertionError: If any of the checks or verifications fail """ # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window( + assert sudo_set_admin_freez_window_extrinsic( subtensor=subtensor, wallet=alice_wallet, window=0, @@ -85,11 +85,11 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) "tempo": subnet_tempo, }, ) - assert sudo_set_sub_subnet_count_extrinsic( + assert sudo_set_mechanism_count_extrinsic( subtensor=subtensor, wallet=alice_wallet, netuid=netuid, - sub_count=2, + mech_count=2, ) # make sure 2 epochs are passed @@ -154,11 +154,11 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) # 3 time doing call if nonce wasn't updated, then raise error @retry.retry(exceptions=Exception, tries=3, delay=1) @execute_and_wait_for_next_nonce(subtensor=subtensor, wallet=alice_wallet) - def set_weights(netuid_, subuid_): + def set_weights(netuid_, mechid_): success, message = subtensor.set_weights( wallet=alice_wallet, netuid=netuid_, - subuid=subuid_, + mechid=mechid_, uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, @@ -173,24 +173,24 @@ def set_weights(netuid_, subuid_): f"{subtensor.substrate.get_account_next_index(alice_wallet.hotkey.ss58_address)}[/orange]" ) - for subuid in range(TESTED_SUB_SUBNETS): + for mechid in range(TESTED_SUB_SUBNETS): # Set weights for each subnet for netuid in netuids: - set_weights(netuid, subuid) + set_weights(netuid, mechid) for netuid in netuids: # Query the Weights storage map for all three subnets weights = subtensor.subnets.weights( netuid=netuid, - subuid=subuid, + mechid=mechid, ) alice_weights = weights[0][1] logging.console.info( - f"Weights for sub-subnet {netuid}.{subuid}: {alice_weights}" + f"Weights for subnet mechanism {netuid}.{mechid}: {alice_weights}" ) assert alice_weights is not None, ( - f"Weights not found for sub-subnet {netuid}.{subuid}" + f"Weights not found for subnet mechanism {netuid}.{mechid}" ) assert alice_weights == list(zip(weight_uids, weight_vals)), ( f"Weights do not match for subnet {netuid}" diff --git a/tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py b/tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py index b00b8b683a..144807ceb3 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py +++ b/tests/unit_tests/extrinsics/asyncex/test_sub_subnet.py @@ -1,59 +1,57 @@ import pytest -from bittensor.core.extrinsics.asyncex import sub_subnet +from bittensor.core.extrinsics.asyncex import mechanism @pytest.mark.asyncio -async def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `commit_sub_weights_extrinsic` extrinsic.""" +async def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] salt = [] - mocked_get_sub_subnet_storage_index = mocker.patch.object( - sub_subnet, "get_sub_subnet_storage_index" - ) - mocked_generate_weight_hash = mocker.patch.object( - sub_subnet, "generate_weight_hash" + mocked_get_mechanism_storage_index = mocker.patch.object( + mechanism, "get_mechid_storage_index" ) + mocked_generate_weight_hash = mocker.patch.object(mechanism, "generate_weight_hash") mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=(True, "") ) # Call - result = await sub_subnet.commit_sub_weights_extrinsic( + result = await mechanism.commit_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, ) # Asserts - mocked_get_sub_subnet_storage_index.assert_called_once_with( - netuid=netuid, subuid=subuid + mocked_get_mechanism_storage_index.assert_called_once_with( + netuid=netuid, mechid=mechid ) mocked_generate_weight_hash.assert_called_once_with( address=fake_wallet.hotkey.ss58_address, - netuid=mocked_get_sub_subnet_storage_index.return_value, + netuid=mocked_get_mechanism_storage_index.return_value, uids=list(uids), values=list(weights), salt=salt, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, ) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="commit_sub_weights", + call_function="commit_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit_hash": mocked_generate_weight_hash.return_value, }, ) @@ -72,19 +70,21 @@ async def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): @pytest.mark.asyncio -async def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `commit_sub_weights_extrinsic` extrinsic.""" +async def test_commit_timelocked_mechanism_weights_extrinsic( + mocker, subtensor, fake_wallet +): + """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] block_time = mocker.Mock() mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - sub_subnet, + mechanism, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -92,11 +92,11 @@ async def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_w mocked_get_subnet_hyperparameters = mocker.patch.object( subtensor, "get_subnet_hyperparameters" ) - mocked_get_sub_subnet_storage_index = mocker.patch.object( - sub_subnet, "get_sub_subnet_storage_index" + mocked_get_mechanism_storage_index = mocker.patch.object( + mechanism, "get_mechid_storage_index" ) mocked_get_encrypted_commit = mocker.patch.object( - sub_subnet, + mechanism, "get_encrypted_commit", return_value=(mocker.Mock(), mocker.Mock()), ) @@ -111,11 +111,11 @@ async def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_w ) # Call - result = await sub_subnet.commit_timelocked_sub_weights_extrinsic( + result = await mechanism.commit_timelocked_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, block_time=block_time, @@ -123,26 +123,26 @@ async def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_w # Asserts mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) - mocked_get_sub_subnet_storage_index.assert_called_once_with( - netuid=netuid, subuid=subuid + mocked_get_mechanism_storage_index.assert_called_once_with( + netuid=netuid, mechid=mechid ) mocked_get_encrypted_commit.assert_called_once_with( uids=uids, weights=weights, subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, tempo=mocked_get_subnet_hyperparameters.return_value.tempo, - netuid=mocked_get_sub_subnet_storage_index.return_value, + netuid=mocked_get_mechanism_storage_index.return_value, current_block=mocked_get_current_block.return_value, block_time=block_time, hotkey=fake_wallet.hotkey.public_key, ) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="commit_timelocked_sub_weights", + call_function="commit_timelocked_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit": mocked_get_encrypted_commit.return_value[0], "reveal_round": mocked_get_encrypted_commit.return_value[1], "commit_reveal_version": 4, @@ -163,19 +163,19 @@ async def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_w @pytest.mark.asyncio -async def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `reveal_sub_weights_extrinsic` extrinsic.""" +async def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `reveal_mechanism_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] salt = [] mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - sub_subnet, + mechanism, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -185,29 +185,29 @@ async def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): ) # Call - result = await sub_subnet.reveal_sub_weights_extrinsic( + result = await mechanism.reveal_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, ) # Asserts mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="reveal_sub_weights", + call_function="reveal_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "uids": mocked_convert_and_normalize_weights_and_uids.return_value[0], "values": mocked_convert_and_normalize_weights_and_uids.return_value[0], "salt": salt, - "version_key": sub_subnet.version_as_int, + "version_key": mechanism.version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_awaited_once_with( @@ -225,18 +225,18 @@ async def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): @pytest.mark.asyncio -async def test_set_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Verify that the `set_sub_weights_extrinsic` function works as expected.""" +async def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Verify that the `set_mechanism_weights_extrinsic` function works as expected.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - sub_subnet, + mechanism, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -251,27 +251,27 @@ async def test_set_sub_weights_extrinsic(mocker, subtensor, fake_wallet): ) # Call - result = await sub_subnet.set_sub_weights_extrinsic( + result = await mechanism.set_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, ) # Asserts mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="set_sub_weights", + call_function="set_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "dests": uids, "weights": weights, - "version_key": sub_subnet.version_as_int, + "version_key": mechanism.version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_awaited_once_with( diff --git a/tests/unit_tests/extrinsics/test_sub_subnet.py b/tests/unit_tests/extrinsics/test_sub_subnet.py index a8518c199d..624ffedbf3 100644 --- a/tests/unit_tests/extrinsics/test_sub_subnet.py +++ b/tests/unit_tests/extrinsics/test_sub_subnet.py @@ -1,35 +1,33 @@ import pytest -from bittensor.core.extrinsics import sub_subnet +from bittensor.core.extrinsics import mechanism -def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `commit_sub_weights_extrinsic` extrinsic.""" +def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] salt = [] mocked_get_sub_subnet_storage_index = mocker.patch.object( - sub_subnet, "get_sub_subnet_storage_index" - ) - mocked_generate_weight_hash = mocker.patch.object( - sub_subnet, "generate_weight_hash" + mechanism, "get_mechid_storage_index" ) + mocked_generate_weight_hash = mocker.patch.object(mechanism, "generate_weight_hash") mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=(True, "") ) # Call - result = sub_subnet.commit_sub_weights_extrinsic( + result = mechanism.commit_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, @@ -37,7 +35,7 @@ def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): # Asserts mocked_get_sub_subnet_storage_index.assert_called_once_with( - netuid=netuid, subuid=subuid + netuid=netuid, mechid=mechid ) mocked_generate_weight_hash.assert_called_once_with( address=fake_wallet.hotkey.ss58_address, @@ -45,14 +43,14 @@ def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): uids=list(uids), values=list(weights), salt=salt, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, ) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="commit_sub_weights", + call_function="commit_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit_hash": mocked_generate_weight_hash.return_value, }, ) @@ -70,19 +68,19 @@ def test_commit_sub_weights_extrinsic(mocker, subtensor, fake_wallet): assert result == mocked_sign_and_send_extrinsic.return_value -def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `commit_sub_weights_extrinsic` extrinsic.""" +def test_commit_timelocked_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] block_time = mocker.Mock() mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - sub_subnet, + mechanism, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -91,10 +89,10 @@ def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet) subtensor, "get_subnet_hyperparameters" ) mocked_get_sub_subnet_storage_index = mocker.patch.object( - sub_subnet, "get_sub_subnet_storage_index" + mechanism, "get_mechid_storage_index" ) mocked_get_encrypted_commit = mocker.patch.object( - sub_subnet, + mechanism, "get_encrypted_commit", return_value=(mocker.Mock(), mocker.Mock()), ) @@ -109,11 +107,11 @@ def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet) ) # Call - result = sub_subnet.commit_timelocked_sub_weights_extrinsic( + result = mechanism.commit_timelocked_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, block_time=block_time, @@ -122,13 +120,13 @@ def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet) # Asserts mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_get_sub_subnet_storage_index.assert_called_once_with( - netuid=netuid, subuid=subuid + netuid=netuid, mechid=mechid ) mocked_get_encrypted_commit.assert_called_once_with( uids=uids, weights=weights, subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, tempo=mocked_get_subnet_hyperparameters.return_value.tempo, netuid=mocked_get_sub_subnet_storage_index.return_value, current_block=mocked_get_current_block.return_value, @@ -137,10 +135,10 @@ def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet) ) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="commit_timelocked_sub_weights", + call_function="commit_timelocked_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "commit": mocked_get_encrypted_commit.return_value[0], "reveal_round": mocked_get_encrypted_commit.return_value[1], "commit_reveal_version": 4, @@ -160,19 +158,19 @@ def test_commit_timelocked_sub_weights_extrinsic(mocker, subtensor, fake_wallet) assert result == mocked_sign_and_send_extrinsic.return_value -def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `reveal_sub_weights_extrinsic` extrinsic.""" +def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `reveal_mechanism_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] salt = [] mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - sub_subnet, + mechanism, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -182,29 +180,29 @@ def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): ) # Call - result = sub_subnet.reveal_sub_weights_extrinsic( + result = mechanism.reveal_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, salt=salt, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, ) # Asserts mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="reveal_sub_weights", + call_function="reveal_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "uids": mocked_convert_and_normalize_weights_and_uids.return_value[0], "values": mocked_convert_and_normalize_weights_and_uids.return_value[0], "salt": salt, - "version_key": sub_subnet.version_as_int, + "version_key": mechanism.version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( @@ -221,18 +219,18 @@ def test_reveal_sub_weights_extrinsic(mocker, subtensor, fake_wallet): assert result == mocked_sign_and_send_extrinsic.return_value -def test_set_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Verify that the `set_sub_weights_extrinsic` function works as expected.""" +def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): + """Verify that the `set_mechanism_weights_extrinsic` function works as expected.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" netuid = mocker.Mock() - subuid = mocker.Mock() + mechid = mocker.Mock() uids = [] weights = [] mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - sub_subnet, + mechanism, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -247,27 +245,27 @@ def test_set_sub_weights_extrinsic(mocker, subtensor, fake_wallet): ) # Call - result = sub_subnet.set_sub_weights_extrinsic( + result = mechanism.set_mechanism_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, - subuid=subuid, + mechid=mechid, uids=uids, weights=weights, - version_key=sub_subnet.version_as_int, + version_key=mechanism.version_as_int, ) # Asserts mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="set_sub_weights", + call_function="set_mechanism_weights", call_params={ "netuid": netuid, - "subid": subuid, + "mecid": mechid, "dests": uids, "weights": weights, - "version_key": sub_subnet.version_as_int, + "version_key": mechanism.version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index d00ca7a0d1..f1aca18380 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2773,7 +2773,7 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): mocked_set_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success")) mocker.patch.object( - async_subtensor, "set_sub_weights_extrinsic", mocked_set_weights_extrinsic + async_subtensor, "set_mechanism_weights_extrinsic", mocked_set_weights_extrinsic ) # Call @@ -2803,7 +2803,7 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): wait_for_inclusion=False, weights=fake_weights, period=8, - subuid=0, + mechid=0, ) mocked_weights_rate_limit.assert_called_once_with(fake_netuid) assert result is True @@ -2833,7 +2833,7 @@ async def test_set_weights_with_exception(subtensor, fake_wallet, mocker): side_effect=Exception("Test exception") ) mocker.patch.object( - async_subtensor, "set_sub_weights_extrinsic", mocked_set_weights_extrinsic + async_subtensor, "set_mechanism_weights_extrinsic", mocked_set_weights_extrinsic ) # Call @@ -2908,7 +2908,9 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): mocked_commit_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success")) mocker.patch.object( - async_subtensor, "commit_sub_weights_extrinsic", mocked_commit_weights_extrinsic + async_subtensor, + "commit_mechanism_weights_extrinsic", + mocked_commit_weights_extrinsic, ) # Call @@ -2932,7 +2934,7 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): wait_for_inclusion=False, wait_for_finalization=False, period=16, - subuid=0, + mechid=0, ) assert result is True assert message == "Success" @@ -2952,7 +2954,9 @@ async def test_commit_weights_with_exception(subtensor, fake_wallet, mocker): side_effect=Exception("Test exception") ) mocker.patch.object( - async_subtensor, "commit_sub_weights_extrinsic", mocked_commit_weights_extrinsic + async_subtensor, + "commit_mechanism_weights_extrinsic", + mocked_commit_weights_extrinsic, ) # Call @@ -4140,8 +4144,8 @@ async def test_get_timelocked_weight_commits(subtensor, mocker): @pytest.mark.asyncio -async def test_get_sub_all_metagraphs_returns_none(subtensor, mocker): - """Verify that `get_sub_all_metagraphs` method returns None.""" +async def test_get_all_mechagraphs_returns_none(subtensor, mocker): + """Verify that `get_all_mechagraphs_info` method returns None.""" # Preps mocker.patch.object(subtensor.substrate, "runtime_call", return_value=None) mocked_metagraph_info_list_from_dicts = mocker.patch( @@ -4149,19 +4153,19 @@ async def test_get_sub_all_metagraphs_returns_none(subtensor, mocker): ) # Call - result = await subtensor.get_sub_all_metagraphs() + result = await subtensor.get_all_mechagraphs_info() # Asserts subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_not_called() assert result is None @pytest.mark.asyncio -async def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): - """Verify that `get_sub_all_metagraphs` method processed the data correctly.""" +async def test_get_all_mechagraphs_happy_path(subtensor, mocker): + """Verify that `get_all_mechagraphs_info` method processed the data correctly.""" # Preps mocked_result = mocker.MagicMock() mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) @@ -4170,11 +4174,11 @@ async def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): ) # Call - result = await subtensor.get_sub_all_metagraphs() + result = await subtensor.get_all_mechagraphs_info() # Asserts subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) assert result == mocked_metagraph_info_list_from_dicts.return_value @@ -4188,10 +4192,10 @@ async def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): ), ) @pytest.mark.asyncio -async def test_get_sub_subnets_emission_split( +async def test_get_mechanism_emission_split( subtensor, mocker, query_return, expected_result ): - """Verify that get_sub_subnets_emission_split calls the correct methods.""" + """Verify that get_mechanism_emission_split calls the correct methods.""" # Preps netuid = mocker.Mock() query_return = ( @@ -4204,13 +4208,13 @@ async def test_get_sub_subnets_emission_split( # Call - result = await subtensor.get_sub_subnets_emission_split(netuid) + result = await subtensor.get_mechanism_emission_split(netuid) # Asserts mocked_determine_block_hash.assert_awaited_once() mocked_query.assert_awaited_once_with( module="SubtensorModule", - storage_function="SubsubnetEmissionSplit", + storage_function="MechanismEmissionSplit", params=[netuid], block_hash=mocked_determine_block_hash.return_value, ) @@ -4218,11 +4222,11 @@ async def test_get_sub_subnets_emission_split( @pytest.mark.asyncio -async def test_get_sub_metagraph_info_returns_none(subtensor, mocker): - """Verify that `get_sub_metagraph_info` method returns None.""" +async def test_get_mechagraph_info_returns_none(subtensor, mocker): + """Verify that `get_mechagraph_info` method returns None.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_result = None @@ -4232,14 +4236,14 @@ async def test_get_sub_metagraph_info_returns_none(subtensor, mocker): ) # Call - result = await subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + result = await subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_awaited_once_with( api="SubnetInfoRuntimeApi", - method="get_submetagraph", - params=[netuid, subuid], + method="get_mechagraph", + params=[netuid, mechid], block_hash=mocked_determine_block_hash.return_value, ) assert result is mocked_result @@ -4247,11 +4251,11 @@ async def test_get_sub_metagraph_info_returns_none(subtensor, mocker): @pytest.mark.asyncio -async def test_get_sub_metagraph_info_happy_path(subtensor, mocker): - """Verify that `get_sub_metagraph_info` method processed the data correctly.""" +async def test_get_mechagraph_info_happy_path(subtensor, mocker): + """Verify that `get_mechagraph_info` method processed the data correctly.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_result = mocker.MagicMock() @@ -4261,14 +4265,14 @@ async def test_get_sub_metagraph_info_happy_path(subtensor, mocker): ) # Call - result = await subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + result = await subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_awaited_once_with( api="SubnetInfoRuntimeApi", - method="get_submetagraph", - params=[netuid, subuid], + method="get_mechagraph", + params=[netuid, mechid], block_hash=mocked_determine_block_hash.return_value, ) mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) @@ -4276,11 +4280,11 @@ async def test_get_sub_metagraph_info_happy_path(subtensor, mocker): @pytest.mark.asyncio -async def test_get_sub_selective_metagraph_returns_none(subtensor, mocker): - """Verify that `get_sub_selective_metagraph` method returns None.""" +async def test_get_selective_mechagraph_info_returns_none(subtensor, mocker): + """Verify that `get_selective_mechagraph_info` method returns None.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 field_indices = [0, 1, 73] mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") @@ -4291,16 +4295,16 @@ async def test_get_sub_selective_metagraph_returns_none(subtensor, mocker): ) # Call - result = await subtensor.get_sub_selective_metagraph( - netuid=netuid, subuid=subuid, field_indices=field_indices + result = await subtensor.get_selective_mechagraph_info( + netuid=netuid, mechid=mechid, field_indices=field_indices ) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_awaited_once_with( api="SubnetInfoRuntimeApi", - method="get_selective_submetagraph", - params=[netuid, subuid, field_indices], + method="get_selective_mechagraph", + params=[netuid, mechid, field_indices], block_hash=mocked_determine_block_hash.return_value, ) assert result is mocked_result @@ -4308,11 +4312,11 @@ async def test_get_sub_selective_metagraph_returns_none(subtensor, mocker): @pytest.mark.asyncio -async def test_get_sub_selective_metagraph_happy_path(subtensor, mocker): - """Verify that `get_sub_selective_metagraph` method processed the data correctly.""" +async def test_get_selective_mechagraph_info_happy_path(subtensor, mocker): + """Verify that `get_selective_mechagraph_info` method processed the data correctly.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 field_indices = [0, 1, 73] mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") @@ -4323,16 +4327,16 @@ async def test_get_sub_selective_metagraph_happy_path(subtensor, mocker): ) # Call - result = await subtensor.get_sub_selective_metagraph( - netuid=netuid, subuid=subuid, field_indices=field_indices + result = await subtensor.get_selective_mechagraph_info( + netuid=netuid, mechid=mechid, field_indices=field_indices ) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_awaited_once_with( api="SubnetInfoRuntimeApi", - method="get_selective_submetagraph", - params=[netuid, subuid, field_indices], + method="get_selective_mechagraph", + params=[netuid, mechid, field_indices], block_hash=mocked_determine_block_hash.return_value, ) mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) @@ -4340,8 +4344,8 @@ async def test_get_sub_selective_metagraph_happy_path(subtensor, mocker): @pytest.mark.asyncio -async def test_get_sub_subnet_count(subtensor, mocker): - """Verify that `get_sub_subnet_count` method processed the data correctly.""" +async def test_get_mechanism_count(subtensor, mocker): + """Verify that `get_mechanism_count` method processed the data correctly.""" # Preps netuid = 14 @@ -4351,13 +4355,13 @@ async def test_get_sub_subnet_count(subtensor, mocker): mocked_query = mocker.patch.object(subtensor.substrate, "query") # Call - result = await subtensor.get_sub_subnet_count(netuid=netuid) + result = await subtensor.get_mechanism_count(netuid=netuid) # Asserts mocked_determine_block_hash.assert_called_once() mocked_query.assert_called_once_with( module="SubtensorModule", - storage_function="SubsubnetCountCurrent", + storage_function="MechanismCountCurrent", params=[netuid], block_hash=mocked_determine_block_hash.return_value, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 5edb1265f6..8ac031ebbe 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1170,7 +1170,9 @@ def test_set_weights(subtensor, mocker, fake_wallet): subtensor.weights_rate_limit = mocked_weights_rate_limit mocked_set_weights_extrinsic = mocker.patch.object( - subtensor_module, "set_sub_weights_extrinsic", return_value=expected_result + subtensor_module, + "set_mechanism_weights_extrinsic", + return_value=expected_result, ) # Call @@ -1203,7 +1205,7 @@ def test_set_weights(subtensor, mocker, fake_wallet): wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, period=8, - subuid=0, + mechid=0, ) assert result == expected_result @@ -1954,7 +1956,9 @@ def test_commit_weights(subtensor, fake_wallet, mocker): expected_result = (True, None) mocked_commit_weights_extrinsic = mocker.patch.object( - subtensor_module, "commit_sub_weights_extrinsic", return_value=expected_result + subtensor_module, + "commit_mechanism_weights_extrinsic", + return_value=expected_result, ) # Call @@ -1981,7 +1985,7 @@ def test_commit_weights(subtensor, fake_wallet, mocker): wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=16, - subuid=0, + mechid=0, ) assert result == expected_result @@ -1995,7 +1999,9 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): salt = [4, 2, 2, 1] expected_result = (True, None) mocked_extrinsic = mocker.patch.object( - subtensor_module, "reveal_sub_weights_extrinsic", return_value=expected_result + subtensor_module, + "reveal_mechanism_weights_extrinsic", + return_value=expected_result, ) # Call @@ -2022,7 +2028,7 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): wait_for_inclusion=False, wait_for_finalization=False, period=16, - subuid=0, + mechid=0, ) @@ -2039,7 +2045,7 @@ def test_reveal_weights_false(subtensor, fake_wallet, mocker): "No attempt made. Perhaps it is too soon to reveal weights!", ) mocked_extrinsic = mocker.patch.object( - subtensor_module, "reveal_sub_weights_extrinsic" + subtensor_module, "reveal_mechanism_weights_extrinsic" ) # Call @@ -3145,10 +3151,10 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): mocked_commit_reveal_enabled = mocker.patch.object( subtensor, "commit_reveal_enabled", return_value=True ) - mocked_commit_timelocked_sub_weights_extrinsic = mocker.patch.object( - subtensor_module, "commit_timelocked_sub_weights_extrinsic" + mocked_commit_timelocked_mechanism_weights_extrinsic = mocker.patch.object( + subtensor_module, "commit_timelocked_mechanism_weights_extrinsic" ) - mocked_commit_timelocked_sub_weights_extrinsic.return_value = ( + mocked_commit_timelocked_mechanism_weights_extrinsic.return_value = ( True, "Weights committed successfully", ) @@ -3167,7 +3173,7 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): # Asserts mocked_commit_reveal_enabled.assert_called_once_with(netuid=fake_netuid) - mocked_commit_timelocked_sub_weights_extrinsic.assert_called_once_with( + mocked_commit_timelocked_mechanism_weights_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -3179,9 +3185,9 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): block_time=12.0, period=8, commit_reveal_version=4, - subuid=0, + mechid=0, ) - assert result == mocked_commit_timelocked_sub_weights_extrinsic.return_value + assert result == mocked_commit_timelocked_mechanism_weights_extrinsic.return_value def test_connection_limit(mocker): @@ -4332,8 +4338,8 @@ def test_get_timelocked_weight_commits(subtensor, mocker): assert result == [] -def test_get_sub_all_metagraphs_returns_none(subtensor, mocker): - """Verify that `get_sub_all_metagraphs` method returns None.""" +def test_get_all_mechagraphs_info_returns_none(subtensor, mocker): + """Verify that `get_all_mechagraphs_info` method returns None.""" # Preps mocker.patch.object(subtensor.substrate, "runtime_call", return_value=None) mocked_metagraph_info_list_from_dicts = mocker.patch( @@ -4341,18 +4347,18 @@ def test_get_sub_all_metagraphs_returns_none(subtensor, mocker): ) # Call - result = subtensor.get_sub_all_metagraphs() + result = subtensor.get_all_mechagraphs_info() # Asserts subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_not_called() assert result is None -def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): - """Verify that `get_sub_all_metagraphs` method processed the data correctly.""" +def test_get_all_mechagraphs_info_happy_path(subtensor, mocker): + """Verify that `get_all_mechagraphs_info` method processed the data correctly.""" # Preps mocked_result = mocker.MagicMock() mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) @@ -4361,11 +4367,11 @@ def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): ) # Call - result = subtensor.get_sub_all_metagraphs() + result = subtensor.get_all_mechagraphs_info() # Asserts subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", method="get_all_submetagraphs", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) assert result == mocked_metagraph_info_list_from_dicts.return_value @@ -4378,10 +4384,8 @@ def test_get_sub_all_metagraphs_happy_path(subtensor, mocker): [None, None], ), ) -def test_get_sub_subnets_emission_split( - subtensor, mocker, query_return, expected_result -): - """Verify that get_sub_subnets_emission_split calls the correct methods.""" +def test_get_mechanism_emission_split(subtensor, mocker, query_return, expected_result): + """Verify that get_mechanism_emission_split calls the correct methods.""" # Preps netuid = mocker.Mock() query_return = ( @@ -4394,24 +4398,24 @@ def test_get_sub_subnets_emission_split( # Call - result = subtensor.get_sub_subnets_emission_split(netuid) + result = subtensor.get_mechanism_emission_split(netuid) # Asserts mocked_determine_block_hash.assert_called_once() mocked_query.assert_called_once_with( module="SubtensorModule", - storage_function="SubsubnetEmissionSplit", + storage_function="MechanismEmissionSplit", params=[netuid], block_hash=mocked_determine_block_hash.return_value, ) assert result == expected_result -def test_get_sub_metagraph_info_returns_none(subtensor, mocker): - """Verify that `get_sub_metagraph_info` method returns None.""" +def test_get_mechagraph_info_returns_none(subtensor, mocker): + """Verify that `get_mechagraph_info` method returns None.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_result = None @@ -4421,25 +4425,25 @@ def test_get_sub_metagraph_info_returns_none(subtensor, mocker): ) # Call - result = subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + result = subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_called_once_with( api="SubnetInfoRuntimeApi", - method="get_submetagraph", - params=[netuid, subuid], + method="get_mechagraph", + params=[netuid, mechid], block_hash=mocked_determine_block_hash.return_value, ) assert result is mocked_result mocked_metagraph_info_from_dict.assert_not_called() -def test_get_sub_metagraph_info_happy_path(subtensor, mocker): - """Verify that `get_sub_metagraph_info` method processed the data correctly.""" +def test_get_mechagraph_info_happy_path(subtensor, mocker): + """Verify that `get_mechagraph_info` method processed the data correctly.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_result = mocker.MagicMock() @@ -4449,25 +4453,25 @@ def test_get_sub_metagraph_info_happy_path(subtensor, mocker): ) # Call - result = subtensor.get_sub_metagraph_info(netuid=netuid, subuid=subuid) + result = subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_called_once_with( api="SubnetInfoRuntimeApi", - method="get_submetagraph", - params=[netuid, subuid], + method="get_mechagraph", + params=[netuid, mechid], block_hash=mocked_determine_block_hash.return_value, ) mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) assert result is mocked_metagraph_info_from_dict.return_value -def test_get_sub_selective_metagraph_returns_none(subtensor, mocker): - """Verify that `get_sub_selective_metagraph` method returns None.""" +def test_get_selective_mechagraph_info_returns_none(subtensor, mocker): + """Verify that `get_selective_mechagraph_info` method returns None.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 field_indices = [0, 1, 73] mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") @@ -4478,27 +4482,27 @@ def test_get_sub_selective_metagraph_returns_none(subtensor, mocker): ) # Call - result = subtensor.get_sub_selective_metagraph( - netuid=netuid, subuid=subuid, field_indices=field_indices + result = subtensor.get_selective_mechagraph_info( + netuid=netuid, mechid=mechid, field_indices=field_indices ) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_called_once_with( api="SubnetInfoRuntimeApi", - method="get_selective_submetagraph", - params=[netuid, subuid, field_indices], + method="get_selective_mechagraph", + params=[netuid, mechid, field_indices], block_hash=mocked_determine_block_hash.return_value, ) assert result is mocked_result mocked_metagraph_info_from_dict.assert_not_called() -def test_get_sub_selective_metagraph_happy_path(subtensor, mocker): - """Verify that `get_sub_selective_metagraph` method processed the data correctly.""" +def test_get_selective_mechagraph_info_happy_path(subtensor, mocker): + """Verify that `get_selective_mechagraph_info` method processed the data correctly.""" # Preps netuid = 14 - subuid = 5 + mechid = 5 field_indices = [0, 1, 73] mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") @@ -4509,24 +4513,24 @@ def test_get_sub_selective_metagraph_happy_path(subtensor, mocker): ) # Call - result = subtensor.get_sub_selective_metagraph( - netuid=netuid, subuid=subuid, field_indices=field_indices + result = subtensor.get_selective_mechagraph_info( + netuid=netuid, mechid=mechid, field_indices=field_indices ) # Asserts mocked_determine_block_hash.assert_called_once() subtensor.substrate.runtime_call.assert_called_once_with( api="SubnetInfoRuntimeApi", - method="get_selective_submetagraph", - params=[netuid, subuid, field_indices], + method="get_selective_mechagraph", + params=[netuid, mechid, field_indices], block_hash=mocked_determine_block_hash.return_value, ) mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) assert result is mocked_metagraph_info_from_dict.return_value -def test_get_sub_subnet_count(subtensor, mocker): - """Verify that `get_sub_subnet_count` method processed the data correctly.""" +def test_get_mechanism_count(subtensor, mocker): + """Verify that `get_mechanism_count` method processed the data correctly.""" # Preps netuid = 14 @@ -4536,13 +4540,13 @@ def test_get_sub_subnet_count(subtensor, mocker): mocked_query = mocker.patch.object(subtensor.substrate, "query") # Call - result = subtensor.get_sub_subnet_count(netuid=netuid) + result = subtensor.get_mechanism_count(netuid=netuid) # Asserts mocked_determine_block_hash.assert_called_once() mocked_query.assert_called_once_with( module="SubtensorModule", - storage_function="SubsubnetCountCurrent", + storage_function="MechanismCountCurrent", params=[netuid], block_hash=mocked_determine_block_hash.return_value, ) From 48f6872d581cc8fc96670af3f43d6bcbac1f09b6 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 17 Sep 2025 16:19:03 -0700 Subject: [PATCH 26/39] tests improvement --- tests/e2e_tests/test_liquid_alpha.py | 2 +- tests/e2e_tests/test_metagraph.py | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 96eccb7419..f505b093ec 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -119,7 +119,7 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): # Test needs to wait for the amount of tempo in the chain equal to OwnerHyperparamRateLimit owner_hyperparam_ratelimit = subtensor.substrate.query( module="SubtensorModule", storage_function="OwnerHyperparamRateLimit" - ) + ).value logging.console.info( f"OwnerHyperparamRateLimit is {owner_hyperparam_ratelimit} tempo(s)." ) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index bcecfa2e5c..b1b6d5ccc9 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -685,9 +685,11 @@ def test_blocks(subtensor): - Wait for block """ - block = subtensor.get_current_block() + get_current_block = subtensor.get_current_block() + block = subtensor.block - assert block == subtensor.block + # Several random tests fell during the block finalization period. Fast blocks of 0.25 seconds (very fast) + assert get_current_block in [block, block + 1] block_hash = subtensor.get_block_hash(block) @@ -695,4 +697,4 @@ def test_blocks(subtensor): subtensor.wait_for_block(block + 10) - assert subtensor.get_current_block() == block + 10 + assert subtensor.get_current_block() in [block + 10, block + 11] From 46ccf64b2f6efd188ef2aeaff205ac539cebd4a7 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 07:45:43 -0700 Subject: [PATCH 27/39] fix runtime call method + tests --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/subtensor.py | 2 +- tests/unit_tests/test_async_subtensor.py | 4 ++-- tests/unit_tests/test_subtensor.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 3cb8a9e05e..5919314cad 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2698,7 +2698,7 @@ async def get_all_mechagraphs_info( block_hash = await self.determine_block_hash(block, block_hash, reuse_block) query = await self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_all_mechagraphs_info", + method="get_all_mechagraphs", block_hash=block_hash, ) if query is None or query.value is None: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 6cc16bf2f4..0d0678b3f7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1903,7 +1903,7 @@ def get_all_mechagraphs_info( block_hash = self.determine_block_hash(block) query = self.substrate.runtime_call( api="SubnetInfoRuntimeApi", - method="get_all_mechagraphs_info", + method="get_all_mechagraphs", block_hash=block_hash, ) if query is None or query.value is None: diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index f1aca18380..ae558dca2e 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4157,7 +4157,7 @@ async def test_get_all_mechagraphs_returns_none(subtensor, mocker): # Asserts subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_not_called() assert result is None @@ -4178,7 +4178,7 @@ async def test_get_all_mechagraphs_happy_path(subtensor, mocker): # Asserts subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) assert result == mocked_metagraph_info_list_from_dicts.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 8ac031ebbe..cad0b3c645 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4351,7 +4351,7 @@ def test_get_all_mechagraphs_info_returns_none(subtensor, mocker): # Asserts subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_not_called() assert result is None @@ -4371,7 +4371,7 @@ def test_get_all_mechagraphs_info_happy_path(subtensor, mocker): # Asserts subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs_info", block_hash=None + api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None ) mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) assert result == mocked_metagraph_info_list_from_dicts.return_value From 76874424fb11287688db5d73219cde6cc146087a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 13:43:37 -0700 Subject: [PATCH 28/39] Improve MetagraphInfo calls --- bittensor/core/async_subtensor.py | 227 +++++++++-------------------- bittensor/core/subtensor.py | 231 ++++++++++++------------------ 2 files changed, 157 insertions(+), 301 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5919314cad..cbc37c5205 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2073,6 +2073,7 @@ async def get_minimum_required_stake(self): return Balance.from_rao(getattr(result, "value", 0)) + # TODO: update parameters order in SDKv10, rename `field_indices` to `selected_indices` async def get_metagraph_info( self, netuid: int, @@ -2080,6 +2081,7 @@ async def get_metagraph_info( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, + mechid: int = 0, ) -> Optional[MetagraphInfo]: """ Retrieves full or partial metagraph information for the specified subnet (netuid). @@ -2090,23 +2092,33 @@ async def get_metagraph_info( Arguments: netuid: The unique identifier of the subnet to query. - field_indices: An optional list of SelectiveMetagraphIndex or int values specifying which fields to - retrieve. If not provided, all available fields will be returned. - block: the block number at which to retrieve the hyperparameter. Do not specify if using block_hash or - reuse_block - block_hash: The hash of blockchain block number for the query. Do not specify if using - block or reuse_block - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + field_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + If not provided, all available fields will be returned. + block: The blockchain block number for the query. + block_hash: The hash of the blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used block hash when retrieving info. + mechid: Subnet mechanism unique identifier. Returns: - Optional[MetagraphInfo]: A MetagraphInfo object containing the requested subnet data, or None if the subnet - with the given netuid does not exist. + MetagraphInfo object with the requested subnet mechanism data, None if the subnet mechanism does not exist. Example: - meta_info = await subtensor.get_metagraph_info(netuid=2) + # Retrieve all fields from the metagraph from subnet 2 mechanism 0 + meta_info = subtensor.get_metagraph_info(netuid=2) + + # Retrieve all fields from the metagraph from subnet 2 mechanism 1 + meta_info = subtensor.get_metagraph_info(netuid=2, mechid=1) + + # Retrieve selective data from the metagraph from subnet 2 mechanism 0 + partial_meta_info = subtensor.get_metagraph_info( + netuid=2, + field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + ) - partial_meta_info = await subtensor.get_metagraph_info( + # Retrieve selective data from the metagraph from subnet 2 mechanism 1 + partial_meta_info = subtensor.get_metagraph_info( netuid=2, + mechid=1, field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] ) @@ -2119,57 +2131,48 @@ async def get_metagraph_info( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - if field_indices: - if isinstance(field_indices, list) and all( - isinstance(f, (SelectiveMetagraphIndex, int)) for f in field_indices - ): - indexes = [ - f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in field_indices - ] - else: - raise ValueError( - "`field_indices` must be a list of SelectiveMetagraphIndex enums or ints." - ) + indexes = ( + [ + f.value if isinstance(f, SelectiveMetagraphIndex) else f + for f in field_indices + ] + if field_indices is not None + else [f for f in range(len(SelectiveMetagraphIndex))] + ) - query = await self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_selective_metagraph", - params=[netuid, indexes if 0 in indexes else [0] + indexes], - block_hash=block_hash, - ) - else: - query = await self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_metagraph", - params=[netuid], - block_hash=block_hash, + query = await self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", + params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], + block_hash=block_hash, + ) + if query is None or not hasattr(query, "value"): + logging.error( + f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) - - if query.value is None: - logging.error(f"Subnet {netuid} does not exist.") return None return MetagraphInfo.from_dict(query.value) + # TODO: update parameters order in SDKv10 async def get_all_metagraphs_info( self, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list[MetagraphInfo]: + all_mechanisms: bool = False, + ) -> Optional[list[MetagraphInfo]]: """ Retrieves a list of MetagraphInfo objects for all subnets - Arguments: - block: the block number at which to retrieve the hyperparameter. Do not specify if using block_hash or - reuse_block - block_hash: The hash of blockchain block number for the query. Do not specify if using - block or reuse_block - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + Parameters: + block: The blockchain block number for the query. + block_hash: The hash of the blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used block hash when retrieving info. + all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. Returns: - MetagraphInfo dataclass + List of MetagraphInfo objects for all existing subnets. Notes: See also: See @@ -2177,12 +2180,16 @@ async def get_all_metagraphs_info( block_hash = await self.determine_block_hash(block, block_hash, reuse_block) if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash + method = "get_all_mechagraphs" if all_mechanisms else "get_all_metagraphs" query = await self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_all_metagraphs", + api="SubnetInfoRuntimeApi", + method=method, block_hash=block_hash, ) - return MetagraphInfo.list_from_dicts(query.decode()) + if query is None or not hasattr(query, "value"): + return None + + return MetagraphInfo.list_from_dicts(query.value) async def get_netuids_for_hotkey( self, @@ -2678,34 +2685,6 @@ async def get_stake_add_fee( amount=amount, netuid=netuid, block=block ) - async def get_all_mechagraphs_info( - self, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Optional[list["MetagraphInfo"]]: - """ - Retrieves all metagraphs for all subnet mechanisms. - - Parameters: - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. - reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. - - Returns: - The list of metagraphs for all subnet mechanisms if found, `None` otherwise. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_all_mechagraphs", - block_hash=block_hash, - ) - if query is None or query.value is None: - return None - - return MetagraphInfo.list_from_dicts(query.value) - async def get_mechanism_emission_split( self, netuid: int, @@ -2737,80 +2716,6 @@ async def get_mechanism_emission_split( return [round(i / sum(result.value) * 100) for i in result.value] - async def get_mechagraph_info( - self, - netuid: int, - mechid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Optional["MetagraphInfo"]: - """ - Retrieves metagraph information for the specified subnet mechanism (netuid, mechid). - - Parameters: - netuid: Subnet identifier. - mechid: Subnet mechanism identifier. - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. - reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. - - Returns: - A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_mechagraph", - params=[netuid, mechid], - block_hash=block_hash, - ) - if query is None or query.value is None: - logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") - return None - - return MetagraphInfo.from_dict(query.value) - - async def get_selective_mechagraph_info( - self, - netuid: int, - mechid: int, - field_indices: Union[list[SelectiveMetagraphIndex], list[int]], - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Optional["MetagraphInfo"]: - """ - Retrieves selective metagraph information for the specified subnet mechanism (netuid, mechid). - - Parameters: - netuid: Subnet identifier. - mechid: Subnet mechanism identifier. - field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. - reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. - - Returns: - A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - indexes = [ - f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in field_indices - ] - query = await self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], - block_hash=block_hash, - ) - if query is None or query.value is None: - logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") - return None - - return MetagraphInfo.from_dict(query.value) - async def get_mechanism_count( self, netuid: int, @@ -3820,21 +3725,24 @@ async def max_weight_limit( return None if call is None else u16_normalized_float(int(call)) async def metagraph( - self, netuid: int, lite: bool = True, block: Optional[int] = None + self, + netuid: int, + lite: bool = True, + block: Optional[int] = None, + mechid: int = 0, ) -> "AsyncMetagraph": """ - Returns a synced metagraph for a specified subnet within the Bittensor network. The metagraph represents the - network's structure, including neuron connections and interactions. + Returns a synced metagraph for a specified subnet within the Bittensor network. + The metagraph represents the network's structure, including neuron connections and interactions. - Arguments: + Parameters: netuid: The network UID of the subnet to query. - lite: If true, returns a metagraph using a lightweight sync (no weights, no bonds). Default is - ``True``. + lite: If true, returns a metagraph using a lightweight sync (no weights, no bonds). block: Block number for synchronization, or `None` for the latest block. + mechid: Subnet mechanism identifier. Returns: - bittensor.core.metagraph.Metagraph: The metagraph representing the subnet's structure and neuron - relationships. + The metagraph representing the subnet's structure and neuron relationships. The metagraph is an essential tool for understanding the topology and dynamics of the Bittensor network's decentralized architecture, particularly in relation to neuron interconnectivity and consensus processes. @@ -3845,6 +3753,7 @@ async def metagraph( lite=lite, sync=False, subtensor=self, + mechid=mechid, ) await metagraph.sync(block=block, lite=lite, subtensor=self) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0d0678b3f7..409af64815 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -47,6 +47,12 @@ remove_liquidity_extrinsic, toggle_user_liquidity_extrinsic, ) +from bittensor.core.extrinsics.mechanism import ( + commit_mechanism_weights_extrinsic, + commit_timelocked_mechanism_weights_extrinsic, + reveal_mechanism_weights_extrinsic, + set_mechanism_weights_extrinsic, +) from bittensor.core.extrinsics.move_stake import ( transfer_stake_extrinsic, swap_stake_extrinsic, @@ -73,12 +79,6 @@ add_stake_multiple_extrinsic, ) from bittensor.core.extrinsics.start_call import start_call_extrinsic -from bittensor.core.extrinsics.mechanism import ( - commit_mechanism_weights_extrinsic, - commit_timelocked_mechanism_weights_extrinsic, - reveal_mechanism_weights_extrinsic, - set_mechanism_weights_extrinsic, -) from bittensor.core.extrinsics.take import ( decrease_take_extrinsic, increase_take_extrinsic, @@ -1370,89 +1370,106 @@ def get_minimum_required_stake(self) -> Balance: return Balance.from_rao(getattr(result, "value", 0)) - # TODO: metagraph_info and selective_metagraph_info logic should be separated in SDKv10 (2 methods) + # TODO: update parameters order in SDKv10, rename `field_indices` to `selected_indices` def get_metagraph_info( self, netuid: int, field_indices: Optional[Union[list[SelectiveMetagraphIndex], list[int]]] = None, block: Optional[int] = None, + mechid: int = 0, ) -> Optional[MetagraphInfo]: """ - Retrieves full or partial metagraph information for the specified subnet (netuid). + Retrieves full or partial metagraph information for the specified subnet mechanism (netuid, mechid). Arguments: - netuid: The NetUID of the subnet to query. - field_indices: An optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + netuid: Subnet unique identifier. + field_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. If not provided, all available fields will be returned. - block: The block number at which to query the data. If not specified, the current block or one determined - via reuse_block or block_hash will be used. + block: The block number at which to query the data. + mechid: Subnet mechanism unique identifier. Returns: - Optional[MetagraphInfo]: A MetagraphInfo object containing the requested subnet data, or None if the subnet - with the given netuid does not exist. + MetagraphInfo object with the requested subnet mechanism data, None if the subnet mechanism does not exist. Example: + # Retrieve all fields from the metagraph from subnet 2 mechanism 0 meta_info = subtensor.get_metagraph_info(netuid=2) + # Retrieve all fields from the metagraph from subnet 2 mechanism 1 + meta_info = subtensor.get_metagraph_info(netuid=2, mechid=1) + + # Retrieve selective data from the metagraph from subnet 2 mechanism 0 + partial_meta_info = subtensor.get_metagraph_info( + netuid=2, + field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + ) + + # Retrieve selective data from the metagraph from subnet 2 mechanism 1 partial_meta_info = subtensor.get_metagraph_info( netuid=2, + mechid=1, field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] ) + + Notes: + See also: + - + - """ - block_hash = self.determine_block_hash(block) + block_hash = self.determine_block_hash(block=block) - if field_indices: - if isinstance(field_indices, list) and all( - isinstance(f, (SelectiveMetagraphIndex, int)) for f in field_indices - ): - indexes = [ - f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in field_indices - ] - else: - raise ValueError( - "`field_indices` must be a list of SelectiveMetagraphIndex enums or ints." - ) + indexes = ( + [ + f.value if isinstance(f, SelectiveMetagraphIndex) else f + for f in field_indices + ] + if field_indices is not None + else [f for f in range(len(SelectiveMetagraphIndex))] + ) - query = self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_selective_metagraph", - params=[netuid, indexes if 0 in indexes else [0] + indexes], - block_hash=block_hash, - ) - else: - query = self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_metagraph", - params=[netuid], - block_hash=block_hash, + query = self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", + params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], + block_hash=block_hash, + ) + if query is None or not hasattr(query, "value"): + logging.error( + f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) - - if query.value is None: - logging.error(f"Subnet {netuid} does not exist.") return None return MetagraphInfo.from_dict(query.value) + # TODO: update parameters order in SDKv10 def get_all_metagraphs_info( - self, block: Optional[int] = None - ) -> list[MetagraphInfo]: + self, + block: Optional[int] = None, + all_mechanisms: bool = False, + ) -> Optional[list[MetagraphInfo]]: """ Retrieves a list of MetagraphInfo objects for all subnets - Arguments: - block: the block number at which to retrieve the hyperparameter. Do not specify if using block_hash or - reuse_block + Parameters: + block: The blockchain block number for the query. + all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. Returns: - MetagraphInfo dataclass + List of MetagraphInfo objects for all existing subnets. + + Notes: + See also: See """ block_hash = self.determine_block_hash(block) + method = "get_all_mechagraphs" if all_mechanisms else "get_all_metagraphs" query = self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_all_metagraphs", + api="SubnetInfoRuntimeApi", + method=method, block_hash=block_hash, ) + if query is None or not hasattr(query, "value"): + return None + return MetagraphInfo.list_from_dicts(query.value) def get_netuids_for_hotkey( @@ -1887,30 +1904,6 @@ def get_stake_add_fee( """ return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) - def get_all_mechagraphs_info( - self, - block: Optional[int] = None, - ) -> Optional[list["MetagraphInfo"]]: - """ - Retrieves all metagraphs for all subnet mechanisms. - - Parameters: - block: The blockchain block number for the query. - - Returns: - The list of metagraphs for all subnet mechanisms if found, `None` otherwise. - """ - block_hash = self.determine_block_hash(block) - query = self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_all_mechagraphs", - block_hash=block_hash, - ) - if query is None or query.value is None: - return None - - return MetagraphInfo.list_from_dicts(query.value) - def get_mechanism_emission_split( self, netuid: int, block: Optional[int] = None ) -> Optional[list[int]]: @@ -1936,73 +1929,6 @@ def get_mechanism_emission_split( return [round(i / sum(result.value) * 100) for i in result.value] - def get_mechagraph_info( - self, - netuid: int, - mechid: int, - block: Optional[int] = None, - ) -> Optional["MetagraphInfo"]: - """ - Retrieves metagraph information for the specified subnet mechanism (netuid, mechid). - - Parameters: - netuid: Subnet identifier. - mechid: Subnet mechanism identifier. - block: The blockchain block number for the query. - - Returns: - A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. - """ - block_hash = self.determine_block_hash(block) - - query = self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_mechagraph", - params=[netuid, mechid], - block_hash=block_hash, - ) - if query is None or query.value is None: - logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") - return None - - return MetagraphInfo.from_dict(query.value) - - def get_selective_mechagraph_info( - self, - netuid: int, - mechid: int, - field_indices: Union[list[SelectiveMetagraphIndex], list[int]], - block: Optional[int] = None, - ) -> Optional["MetagraphInfo"]: - """ - Retrieves selective metagraph information for the specified subnet mechanism (netuid, mechid). - - Parameters: - netuid: Subnet identifier. - mechid: Subnet mechanism identifier. - field_indices: A list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. - block: The blockchain block number for the query. - - Returns: - A MetagraphInfo object containing the requested mechanism data, or None if the mechanism does not exist. - """ - block_hash = self.determine_block_hash(block=block) - indexes = [ - f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in field_indices - ] - query = self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], - block_hash=block_hash, - ) - if query is None or query.value is None: - logging.error(f"Subnet mechanism {netuid}.{mechid} does not exist.") - return None - - return MetagraphInfo.from_dict(query.value) - def get_mechanism_count( self, netuid: int, @@ -2792,14 +2718,35 @@ def max_weight_limit( return None if call is None else u16_normalized_float(int(call)) def metagraph( - self, netuid: int, lite: bool = True, block: Optional[int] = None + self, + netuid: int, + lite: bool = True, + block: Optional[int] = None, + mechid: int = 0, ) -> "Metagraph": + """ + Returns a synced metagraph for a specified subnet within the Bittensor network. + The metagraph represents the network's structure, including neuron connections and interactions. + + Parameters: + netuid: The network UID of the subnet to query. + lite: If true, returns a metagraph using a lightweight sync (no weights, no bonds). + block: Block number for synchronization, or `None` for the latest block. + mechid: Subnet mechanism identifier. + + Returns: + The metagraph representing the subnet's structure and neuron relationships. + + The metagraph is an essential tool for understanding the topology and dynamics of the Bittensor network's + decentralized architecture, particularly in relation to neuron interconnectivity and consensus processes. + """ metagraph = Metagraph( network=self.chain_endpoint, netuid=netuid, lite=lite, sync=False, subtensor=self, + mechid=mechid, ) metagraph.sync(block=block, lite=lite, subtensor=self) From 137984191888f6b8e9e27bd78b2636fd79f2563c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 13:44:28 -0700 Subject: [PATCH 29/39] improve Metagraph --- bittensor/core/metagraph.py | 93 +++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index af758939ea..2bc0cef868 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -283,6 +283,11 @@ class MetagraphMixin(ABC): pool: MetagraphInfoPool emissions: MetagraphInfoEmissions + # Mechanisms related fields + mechid: int + mechanisms_emissions_split: list[int] + mechanism_count: int + @property def TS(self) -> Tensor: """ @@ -525,6 +530,7 @@ def __init__( lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, + mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the @@ -532,17 +538,17 @@ def __init__( in representing the state of the Bittensor network. Args: - netuid (int): The unique identifier for the network, distinguishing this instance of the metagraph within + netuid: The unique identifier for the network, distinguishing this instance of the metagraph within potentially multiple network configurations. - network (str): The name of the network, which can indicate specific configurations or versions of the - Bittensor network. - lite (bool): A flag indicating whether to use a lite version of the metagraph. The lite version may contain - less detailed information but can be quicker to initialize and sync. - sync (bool): A flag indicating whether to synchronize the metagraph with the network upon initialization. + network: The name of the network, which can indicate specific configurations or versions of the Bittensor + network. + lite: A flag indicating whether to use a lite version of the metagraph. The lite version may contain less + detailed information but can be quicker to initialize and sync. + sync: A flag indicating whether to synchronize the metagraph with the network upon initialization. Synchronization involves updating the metagraph's parameters to reflect the current state of the network. Example: - Initializing a metagraph object for the Bittensor network with a specific network UID:: + Initializing a metagraph object for the Bittensor network with a specific network UID: metagraph = Metagraph(netuid=123, network="finney", lite=True, sync=True) """ @@ -550,6 +556,7 @@ def __init__( self.subtensor = subtensor self.should_sync = sync self.netuid = netuid + self.mechid = mechid self.network, self.chain_endpoint = determine_chain_endpoint_and_network( network ) @@ -1033,31 +1040,32 @@ def __init__( lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, + mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the provided arguments. This class requires Torch to be installed. This method is the entry point for creating a metagraph object, which is a central component in representing the state of the Bittensor network. - Args: - netuid (int): The unique identifier for the network, distinguishing this instance of the metagraph within - potentially multiple network configurations. - network (str): The name of the network, which can indicate specific configurations or versions of the - Bittensor network. - lite (bool): A flag indicating whether to use a lite version of the metagraph. The lite version may contain - less detailed information but can be quicker to initialize and sync. - sync (bool): A flag indicating whether to synchronize the metagraph with the network upon initialization. + Parameters: + netuid: Subnet unique identifier. + network: The name of the network, which can indicate specific configurations or versions of the Bittensor + network. + lite: A flag indicating whether to use a lite version of the metagraph. The lite version may contain less + detailed information but can be quicker to initialize and sync. + sync: A flag indicating whether to synchronize the metagraph with the network upon initialization. Synchronization involves updating the metagraph's parameters to reflect the current state of the network. + mechid: Subnet mechanism unique identifier. Example: - Initializing a metagraph object for the Bittensor network with a specific network UID:: + Initializing a metagraph object for the Bittensor network with a specific network UID: from bittensor.core.metagraph import Metagraph metagraph = Metagraph(netuid=123, network="finney", lite=True, sync=True) """ BaseClass.__init__(self) - MetagraphMixin.__init__(self, netuid, network, lite, sync, subtensor) + MetagraphMixin.__init__(self, netuid, network, lite, sync, subtensor, mechid) self._dtype_registry = { "int64": torch.int64, "float32": torch.float32, @@ -1198,21 +1206,22 @@ def __init__( lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, + mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the provided arguments. This class doesn't require installed Torch. This method is the entry point for creating a metagraph object, which is a central component in representing the state of the Bittensor network. - Args: - netuid (int): The unique identifier for the network, distinguishing this instance of the metagraph within - potentially multiple network configurations. - network (str): The name of the network, which can indicate specific configurations or versions of the - Bittensor network. - lite (bool): A flag indicating whether to use a lite version of the metagraph. The lite version may contain - less detailed information but can be quicker to initialize and sync. - sync (bool): A flag indicating whether to synchronize the metagraph with the network upon initialization. + Parameters: + netuid: Subnet unique identifier. + network: The name of the network, which can indicate specific configurations or versions of the Bittensor + network. + lite: A flag indicating whether to use a lite version of the metagraph. The lite version may contain less + detailed information but can be quicker to initialize and sync. + sync: A flag indicating whether to synchronize the metagraph with the network upon initialization. Synchronization involves updating the metagraph's parameters to reflect the current state of the network. + mechid: Subnet mechanism unique identifier. Example: Initializing a metagraph object for the Bittensor network with a specific network UID:: @@ -1221,7 +1230,7 @@ def __init__( metagraph = Metagraph(netuid=123, network="finney", lite=True, sync=True) """ - MetagraphMixin.__init__(self, netuid, network, lite, sync, subtensor) + MetagraphMixin.__init__(self, netuid, network, lite, sync, subtensor, mechid) self.netuid = netuid self.network, self.chain_endpoint = determine_chain_endpoint_and_network( @@ -1335,8 +1344,9 @@ def __init__( lite: bool = True, sync: bool = True, subtensor: Optional["AsyncSubtensor"] = None, + mechid: int = 0, ): - super().__init__(netuid, network, lite, sync, subtensor) + super().__init__(netuid, network, lite, sync, subtensor, mechid) async def __aenter__(self): if self.should_sync: @@ -1429,7 +1439,7 @@ async def sync( await self._get_all_stakes_from_chain(block=block) # apply MetagraphInfo data to instance - await self._apply_metagraph_info(block=block) + await self._apply_extra_info(block=block) async def _initialize_subtensor( self, subtensor: "AsyncSubtensor" @@ -1639,13 +1649,19 @@ async def _get_all_stakes_from_chain(self, block: int): except (SubstrateRequestException, AttributeError) as e: logging.debug(e) - async def _apply_metagraph_info(self, block: int): + async def _apply_extra_info(self, block: int): """Retrieves metagraph information for a specific subnet and applies it using a mixin.""" metagraph_info = await self.subtensor.get_metagraph_info( - self.netuid, block=block + netuid=self.netuid, mechid=self.mechid, block=block ) if metagraph_info: self._apply_metagraph_info_mixin(metagraph_info=metagraph_info) + self.mechanism_count = await self.subtensor.get_mechanism_count( + netuid=self.netuid, block=block + ) + self.emissions_split = await self.subtensor.get_mechanism_emission_split( + netuid=self.netuid, block=block + ) class Metagraph(NumpyOrTorch): @@ -1656,8 +1672,9 @@ def __init__( lite: bool = True, sync: bool = True, subtensor: Optional["Subtensor"] = None, + mechid: int = 0, ): - super().__init__(netuid, network, lite, sync, subtensor) + super().__init__(netuid, network, lite, sync, subtensor, mechid) if self.should_sync: self.sync() @@ -1746,7 +1763,7 @@ def sync( self._get_all_stakes_from_chain(block=block) # apply MetagraphInfo data to instance - self._apply_metagraph_info(block=block) + self._apply_extra_info(block=block) def _initialize_subtensor(self, subtensor: "Subtensor") -> "Subtensor": """ @@ -1947,11 +1964,19 @@ def _get_all_stakes_from_chain(self, block: int): except (SubstrateRequestException, AttributeError) as e: logging.debug(e) - def _apply_metagraph_info(self, block: int): + def _apply_extra_info(self, block: int): """Retrieves metagraph information for a specific subnet and applies it using a mixin.""" - metagraph_info = self.subtensor.get_metagraph_info(self.netuid, block=block) + metagraph_info = self.subtensor.get_metagraph_info( + netuid=self.netuid, mechid=self.mechid, block=block + ) if metagraph_info: self._apply_metagraph_info_mixin(metagraph_info=metagraph_info) + self.mechanism_count = self.subtensor.get_mechanism_count( + netuid=self.netuid, block=block + ) + self.emissions_split = self.subtensor.get_mechanism_emission_split( + netuid=self.netuid, block=block + ) async def async_metagraph( From 2c9ff5e8713d0977b0a50cfe006d0ccf9eb179a5 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 13:44:48 -0700 Subject: [PATCH 30/39] update SubtensorApi --- bittensor/core/subtensor_api/metagraphs.py | 3 --- bittensor/core/subtensor_api/subnets.py | 3 --- bittensor/core/subtensor_api/utils.py | 5 ----- 3 files changed, 11 deletions(-) diff --git a/bittensor/core/subtensor_api/metagraphs.py b/bittensor/core/subtensor_api/metagraphs.py index 481395336f..af143a1620 100644 --- a/bittensor/core/subtensor_api/metagraphs.py +++ b/bittensor/core/subtensor_api/metagraphs.py @@ -9,7 +9,4 @@ class Metagraphs: def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_metagraph_info = subtensor.get_metagraph_info self.get_all_metagraphs_info = subtensor.get_all_metagraphs_info - self.get_all_mechagraphs_info = subtensor.get_all_mechagraphs_info - self.get_mechagraph_info = subtensor.get_mechagraph_info - self.get_selective_mechagraph_info = subtensor.get_selective_mechagraph_info self.metagraph = subtensor.metagraph diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index 6ea5dcd406..d717017382 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -25,10 +25,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): subtensor.get_neuron_for_pubkey_and_subnet ) self.get_next_epoch_start_block = subtensor.get_next_epoch_start_block - self.get_all_mechagraphs = subtensor.get_all_mechagraphs_info self.get_mechanism_emission_split = subtensor.get_mechanism_emission_split - self.get_mechagraph_info = subtensor.get_mechagraph_info - self.get_selective_mechagraph_info = subtensor.get_selective_mechagraph_info self.get_mechanism_count = subtensor.get_mechanism_count self.get_subnet_burn_cost = subtensor.get_subnet_burn_cost self.get_subnet_hyperparameters = subtensor.get_subnet_hyperparameters diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index e57a2095b6..f842802b82 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -92,14 +92,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_stake_movement_fee = subtensor._subtensor.get_stake_movement_fee subtensor.get_stake_operations_fee = subtensor._subtensor.get_stake_operations_fee subtensor.get_stake_weight = subtensor._subtensor.get_stake_weight - subtensor.get_all_mechagraphs_info = subtensor._subtensor.get_all_mechagraphs_info subtensor.get_mechanism_emission_split = ( subtensor._subtensor.get_mechanism_emission_split ) - subtensor.get_mechagraph_info = subtensor._subtensor.get_mechagraph_info - subtensor.get_selective_mechagraph_info = ( - subtensor._subtensor.get_selective_mechagraph_info - ) subtensor.get_mechanism_count = subtensor._subtensor.get_mechanism_count subtensor.get_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( From 6d67b58f94ece210c99754343e254ee65a793879 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 14:40:57 -0700 Subject: [PATCH 31/39] check query better --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/chain_data/metagraph_info.py | 2 +- bittensor/core/subtensor.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index cbc37c5205..df5c9effc2 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2146,7 +2146,7 @@ async def get_metagraph_info( params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) - if query is None or not hasattr(query, "value"): + if query is None or not hasattr(query, "value") or query.value is None: logging.error( f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index 3e74a408af..69b170c1c1 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -400,7 +400,7 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": else None ), validators=[v for v in decoded["validators"]] - if decoded.get("validators") is not None + if decoded.get("validators") else None, commitments=get_selective_metagraph_commitments(decoded), ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 409af64815..2660aada96 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1433,7 +1433,7 @@ def get_metagraph_info( params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) - if query is None or not hasattr(query, "value"): + if query is None or not hasattr(query, "value") or query.value is None: logging.error( f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) From 6b42893dcdc992a05c4a53a8b7727c4d9479beba Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 14:41:22 -0700 Subject: [PATCH 32/39] tests --- tests/unit_tests/test_async_subtensor.py | 203 ++--------------------- tests/unit_tests/test_subtensor.py | 198 ++-------------------- 2 files changed, 28 insertions(+), 373 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index ae558dca2e..71b2a5a240 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3151,6 +3151,7 @@ async def test_get_metagraph_info_all_fields(subtensor, mocker): """Test get_metagraph_info with all fields (default behavior).""" # Preps netuid = 1 + default_mechid = 0 mock_value = {"mock": "data"} mock_runtime_call = mocker.patch.object( @@ -3170,9 +3171,9 @@ async def test_get_metagraph_info_all_fields(subtensor, mocker): # Asserts assert result == "parsed_metagraph" mock_runtime_call.assert_awaited_once_with( - "SubnetInfoRuntimeApi", - "get_selective_metagraph", - params=[netuid, SelectiveMetagraphIndex.all_indices()], + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", + params=[netuid, default_mechid, SelectiveMetagraphIndex.all_indices()], block_hash=await subtensor.determine_block_hash(None), ) mock_from_dict.assert_called_once_with(mock_value) @@ -3183,6 +3184,7 @@ async def test_get_metagraph_info_specific_fields(subtensor, mocker): """Test get_metagraph_info with specific fields.""" # Preps netuid = 1 + default_mechid = 0 mock_value = {"mock": "data"} fields = [SelectiveMetagraphIndex.Name, 5] @@ -3201,10 +3203,11 @@ async def test_get_metagraph_info_specific_fields(subtensor, mocker): # Asserts assert result == "parsed_metagraph" mock_runtime_call.assert_awaited_once_with( - "SubnetInfoRuntimeApi", - "get_selective_metagraph", + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", params=[ netuid, + default_mechid, [0] + [ f.value if isinstance(f, SelectiveMetagraphIndex) else f for f in fields @@ -3215,34 +3218,15 @@ async def test_get_metagraph_info_specific_fields(subtensor, mocker): mock_from_dict.assert_called_once_with(mock_value) -@pytest.mark.parametrize( - "wrong_fields", - [ - [ - "invalid", - ], - [SelectiveMetagraphIndex.Active, 1, "f"], - [1, 2, 3, "f"], - ], -) -@pytest.mark.asyncio -async def test_get_metagraph_info_invalid_field_indices(subtensor, wrong_fields): - """Test get_metagraph_info raises ValueError on invalid field_indices.""" - with pytest.raises( - ValueError, - match="`field_indices` must be a list of SelectiveMetagraphIndex enums or ints.", - ): - await subtensor.get_metagraph_info(netuid=1, field_indices=wrong_fields) - - @pytest.mark.asyncio async def test_get_metagraph_info_subnet_not_exist(subtensor, mocker): """Test get_metagraph_info returns None when subnet doesn't exist.""" netuid = 1 + default_mechid = 0 mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.AsyncMock(value=None), + return_value=None, ) mocked_logger = mocker.Mock() @@ -3251,7 +3235,9 @@ async def test_get_metagraph_info_subnet_not_exist(subtensor, mocker): result = await subtensor.get_metagraph_info(netuid=netuid) assert result is None - mocked_logger.assert_called_once_with(f"Subnet {netuid} does not exist.") + mocked_logger.assert_called_once_with( + f"Subnet mechanism {netuid}.{default_mechid} does not exist." + ) @pytest.mark.asyncio @@ -4143,47 +4129,6 @@ async def test_get_timelocked_weight_commits(subtensor, mocker): assert result == [] -@pytest.mark.asyncio -async def test_get_all_mechagraphs_returns_none(subtensor, mocker): - """Verify that `get_all_mechagraphs_info` method returns None.""" - # Preps - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=None) - mocked_metagraph_info_list_from_dicts = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" - ) - - # Call - result = await subtensor.get_all_mechagraphs_info() - - # Asserts - subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None - ) - mocked_metagraph_info_list_from_dicts.assert_not_called() - assert result is None - - -@pytest.mark.asyncio -async def test_get_all_mechagraphs_happy_path(subtensor, mocker): - """Verify that `get_all_mechagraphs_info` method processed the data correctly.""" - # Preps - mocked_result = mocker.MagicMock() - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_list_from_dicts = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" - ) - - # Call - result = await subtensor.get_all_mechagraphs_info() - - # Asserts - subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None - ) - mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) - assert result == mocked_metagraph_info_list_from_dicts.return_value - - @pytest.mark.parametrize( "query_return, expected_result", ( @@ -4221,128 +4166,6 @@ async def test_get_mechanism_emission_split( assert result == expected_result -@pytest.mark.asyncio -async def test_get_mechagraph_info_returns_none(subtensor, mocker): - """Verify that `get_mechagraph_info` method returns None.""" - # Preps - netuid = 14 - mechid = 5 - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = None - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = await subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", - method="get_mechagraph", - params=[netuid, mechid], - block_hash=mocked_determine_block_hash.return_value, - ) - assert result is mocked_result - mocked_metagraph_info_from_dict.assert_not_called() - - -@pytest.mark.asyncio -async def test_get_mechagraph_info_happy_path(subtensor, mocker): - """Verify that `get_mechagraph_info` method processed the data correctly.""" - # Preps - netuid = 14 - mechid = 5 - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = mocker.MagicMock() - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = await subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", - method="get_mechagraph", - params=[netuid, mechid], - block_hash=mocked_determine_block_hash.return_value, - ) - mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) - assert result is mocked_metagraph_info_from_dict.return_value - - -@pytest.mark.asyncio -async def test_get_selective_mechagraph_info_returns_none(subtensor, mocker): - """Verify that `get_selective_mechagraph_info` method returns None.""" - # Preps - netuid = 14 - mechid = 5 - field_indices = [0, 1, 73] - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = None - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = await subtensor.get_selective_mechagraph_info( - netuid=netuid, mechid=mechid, field_indices=field_indices - ) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, field_indices], - block_hash=mocked_determine_block_hash.return_value, - ) - assert result is mocked_result - mocked_metagraph_info_from_dict.assert_not_called() - - -@pytest.mark.asyncio -async def test_get_selective_mechagraph_info_happy_path(subtensor, mocker): - """Verify that `get_selective_mechagraph_info` method processed the data correctly.""" - # Preps - netuid = 14 - mechid = 5 - field_indices = [0, 1, 73] - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = mocker.MagicMock() - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = await subtensor.get_selective_mechagraph_info( - netuid=netuid, mechid=mechid, field_indices=field_indices - ) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_awaited_once_with( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, field_indices], - block_hash=mocked_determine_block_hash.return_value, - ) - mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) - assert result is mocked_metagraph_info_from_dict.return_value - - @pytest.mark.asyncio async def test_get_mechanism_count(subtensor, mocker): """Verify that `get_mechanism_count` method processed the data correctly.""" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index cad0b3c645..72c30c8d87 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1018,6 +1018,7 @@ def test_metagraph(subtensor, mocker): """Tests subtensor.metagraph call.""" # Prep fake_netuid = 1 + default_mechid = 0 fake_lite = True mocked_metagraph = mocker.patch.object(subtensor_module, "Metagraph") @@ -1028,6 +1029,7 @@ def test_metagraph(subtensor, mocker): mocked_metagraph.assert_called_once_with( network=subtensor.chain_endpoint, netuid=fake_netuid, + mechid=default_mechid, lite=fake_lite, sync=False, subtensor=subtensor, @@ -3353,6 +3355,7 @@ def test_get_metagraph_info_all_fields(subtensor, mocker): """Test get_metagraph_info with all fields (default behavior).""" # Preps netuid = 1 + default_mechid = 0 mock_value = {"mock": "data"} mock_runtime_call = mocker.patch.object( @@ -3372,9 +3375,9 @@ def test_get_metagraph_info_all_fields(subtensor, mocker): # Asserts assert result == "parsed_metagraph" mock_runtime_call.assert_called_once_with( - "SubnetInfoRuntimeApi", - "get_selective_metagraph", - params=[netuid, SelectiveMetagraphIndex.all_indices()], + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", + params=[netuid, default_mechid, SelectiveMetagraphIndex.all_indices()], block_hash=subtensor.determine_block_hash(None), ) mock_from_dict.assert_called_once_with(mock_value) @@ -3384,6 +3387,7 @@ def test_get_metagraph_info_specific_fields(subtensor, mocker): """Test get_metagraph_info with specific fields.""" # Preps netuid = 1 + default_mechid = 0 mock_value = {"mock": "data"} fields = [SelectiveMetagraphIndex.Name, 5] @@ -3402,10 +3406,11 @@ def test_get_metagraph_info_specific_fields(subtensor, mocker): # Asserts assert result == "parsed_metagraph" mock_runtime_call.assert_called_once_with( - "SubnetInfoRuntimeApi", - "get_selective_metagraph", + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", params=[ netuid, + default_mechid, [0] + [ f.value if isinstance(f, SelectiveMetagraphIndex) else f for f in fields @@ -3416,32 +3421,14 @@ def test_get_metagraph_info_specific_fields(subtensor, mocker): mock_from_dict.assert_called_once_with(mock_value) -@pytest.mark.parametrize( - "wrong_fields", - [ - [ - "invalid", - ], - [SelectiveMetagraphIndex.Active, 1, "f"], - [1, 2, 3, "f"], - ], -) -def test_get_metagraph_info_invalid_field_indices(subtensor, wrong_fields): - """Test get_metagraph_info raises ValueError on invalid field_indices.""" - with pytest.raises( - ValueError, - match="`field_indices` must be a list of SelectiveMetagraphIndex enums or ints.", - ): - subtensor.get_metagraph_info(netuid=1, field_indices=wrong_fields) - - def test_get_metagraph_info_subnet_not_exist(subtensor, mocker): """Test get_metagraph_info returns None when subnet doesn't exist.""" netuid = 1 + default_mechid = 0 mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.Mock(value=None), + return_value=None, ) mocked_logger = mocker.Mock() @@ -3450,7 +3437,9 @@ def test_get_metagraph_info_subnet_not_exist(subtensor, mocker): result = subtensor.get_metagraph_info(netuid=netuid) assert result is None - mocked_logger.assert_called_once_with(f"Subnet {netuid} does not exist.") + mocked_logger.assert_called_once_with( + f"Subnet mechanism {netuid}.{default_mechid} does not exist." + ) def test_blocks_since_last_step_with_value(subtensor, mocker): @@ -4338,45 +4327,6 @@ def test_get_timelocked_weight_commits(subtensor, mocker): assert result == [] -def test_get_all_mechagraphs_info_returns_none(subtensor, mocker): - """Verify that `get_all_mechagraphs_info` method returns None.""" - # Preps - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=None) - mocked_metagraph_info_list_from_dicts = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" - ) - - # Call - result = subtensor.get_all_mechagraphs_info() - - # Asserts - subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None - ) - mocked_metagraph_info_list_from_dicts.assert_not_called() - assert result is None - - -def test_get_all_mechagraphs_info_happy_path(subtensor, mocker): - """Verify that `get_all_mechagraphs_info` method processed the data correctly.""" - # Preps - mocked_result = mocker.MagicMock() - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_list_from_dicts = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.list_from_dicts" - ) - - # Call - result = subtensor.get_all_mechagraphs_info() - - # Asserts - subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", method="get_all_mechagraphs", block_hash=None - ) - mocked_metagraph_info_list_from_dicts.assert_called_once_with(mocked_result.value) - assert result == mocked_metagraph_info_list_from_dicts.return_value - - @pytest.mark.parametrize( "query_return, expected_result", ( @@ -4411,124 +4361,6 @@ def test_get_mechanism_emission_split(subtensor, mocker, query_return, expected_ assert result == expected_result -def test_get_mechagraph_info_returns_none(subtensor, mocker): - """Verify that `get_mechagraph_info` method returns None.""" - # Preps - netuid = 14 - mechid = 5 - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = None - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", - method="get_mechagraph", - params=[netuid, mechid], - block_hash=mocked_determine_block_hash.return_value, - ) - assert result is mocked_result - mocked_metagraph_info_from_dict.assert_not_called() - - -def test_get_mechagraph_info_happy_path(subtensor, mocker): - """Verify that `get_mechagraph_info` method processed the data correctly.""" - # Preps - netuid = 14 - mechid = 5 - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = mocker.MagicMock() - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = subtensor.get_mechagraph_info(netuid=netuid, mechid=mechid) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", - method="get_mechagraph", - params=[netuid, mechid], - block_hash=mocked_determine_block_hash.return_value, - ) - mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) - assert result is mocked_metagraph_info_from_dict.return_value - - -def test_get_selective_mechagraph_info_returns_none(subtensor, mocker): - """Verify that `get_selective_mechagraph_info` method returns None.""" - # Preps - netuid = 14 - mechid = 5 - field_indices = [0, 1, 73] - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = None - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = subtensor.get_selective_mechagraph_info( - netuid=netuid, mechid=mechid, field_indices=field_indices - ) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, field_indices], - block_hash=mocked_determine_block_hash.return_value, - ) - assert result is mocked_result - mocked_metagraph_info_from_dict.assert_not_called() - - -def test_get_selective_mechagraph_info_happy_path(subtensor, mocker): - """Verify that `get_selective_mechagraph_info` method processed the data correctly.""" - # Preps - netuid = 14 - mechid = 5 - field_indices = [0, 1, 73] - - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_result = mocker.MagicMock() - mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_result) - mocked_metagraph_info_from_dict = mocker.patch( - "bittensor.core.chain_data.metagraph_info.MetagraphInfo.from_dict" - ) - - # Call - result = subtensor.get_selective_mechagraph_info( - netuid=netuid, mechid=mechid, field_indices=field_indices - ) - - # Asserts - mocked_determine_block_hash.assert_called_once() - subtensor.substrate.runtime_call.assert_called_once_with( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, field_indices], - block_hash=mocked_determine_block_hash.return_value, - ) - mocked_metagraph_info_from_dict.assert_called_once_with(mocked_result.value) - assert result is mocked_metagraph_info_from_dict.return_value - - def test_get_mechanism_count(subtensor, mocker): """Verify that `get_mechanism_count` method processed the data correctly.""" # Preps From bf2efa622d3f0244467701ece1d508aacbda404d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 14:45:30 -0700 Subject: [PATCH 33/39] TODO --- bittensor/core/async_subtensor.py | 1 + bittensor/core/subtensor.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index df5c9effc2..28676850a9 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3724,6 +3724,7 @@ async def max_weight_limit( ) return None if call is None else u16_normalized_float(int(call)) + # TODO: update parameters order in SDKv10 async def metagraph( self, netuid: int, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 2660aada96..75e7212a20 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2717,6 +2717,7 @@ def max_weight_limit( ) return None if call is None else u16_normalized_float(int(call)) + # TODO: update parameters order in SDKv10 def metagraph( self, netuid: int, From faeb9269cd5048be058be058f1e04e57a387c307 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 14:59:28 -0700 Subject: [PATCH 34/39] They change error back to SubnetNotExists --- bittensor/core/errors.py | 2 +- bittensor/utils/easy_imports.py | 7 +++---- tests/e2e_tests/test_hotkeys.py | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bittensor/core/errors.py b/bittensor/core/errors.py index 602b79206a..e52e994fcd 100644 --- a/bittensor/core/errors.py +++ b/bittensor/core/errors.py @@ -159,7 +159,7 @@ class NotDelegateError(StakeError): """ -class MechanismDoesNotExist(ChainTransactionError): +class SubnetNotExists(ChainTransactionError): """ The subnet does not exist. """ diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index 327955d68b..f71ef909c6 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -25,7 +25,7 @@ decrypt_keyfile_data, Keyfile, ) -from bittensor_wallet.wallet import display_mnemonic_msg, Wallet +from bittensor_wallet.wallet import Wallet from bittensor.core import settings, timelock from bittensor.core.async_subtensor import AsyncSubtensor @@ -86,7 +86,7 @@ RegistrationNotPermittedOnRootSubnet, RunException, StakeError, - MechanismDoesNotExist, + SubnetNotExists, SynapseDendriteNoneException, SynapseParsingError, TooManyChildren, @@ -194,7 +194,6 @@ def info(on: bool = True): "get_coldkey_password_from_environment", "decrypt_keyfile_data", "Keyfile", - "display_mnemonic_msg", "Wallet", "settings", "timelock", @@ -253,7 +252,7 @@ def info(on: bool = True): "RegistrationNotPermittedOnRootSubnet", "RunException", "StakeError", - "MechanismDoesNotExist", + "SubnetNotExists", "SynapseDendriteNoneException", "SynapseParsingError", "TooManyChildren", diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index d17929547e..adbb1fed9b 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -3,7 +3,7 @@ from bittensor.core.errors import ( NotEnoughStakeToSetChildkeys, RegistrationNotPermittedOnRootSubnet, - MechanismDoesNotExist, + SubnetNotExists, InvalidChild, TooManyChildren, ProportionOverflow, @@ -151,7 +151,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w raise_error=True, ) - with pytest.raises(MechanismDoesNotExist): + with pytest.raises(SubnetNotExists): subtensor.set_children( alice_wallet, alice_wallet.hotkey.ss58_address, From 89b4c8f22a0983cd21c8d64657b88d966b6c76bb Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 15:40:57 -0700 Subject: [PATCH 35/39] bring back old error on docstrings --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/extrinsics/asyncex/children.py | 2 +- bittensor/core/extrinsics/children.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 28676850a9..8bafa3f875 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5210,7 +5210,7 @@ async def set_children( 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. - MechanismDoesNotExist: Attempting to register to a non-existent 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. diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index e99b1dbd85..46853642fe 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -44,7 +44,7 @@ async def set_children_extrinsic( 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. - MechanismDoesNotExist: Attempting to register to a non-existent 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. diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 0f85b4de53..dd91fbe97b 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -44,7 +44,7 @@ def set_children_extrinsic( 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. - MechanismDoesNotExist: Attempting to register to a non-existent 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. From 707332eee907ce6f6b95afb6516e2be2726f1385 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 19 Sep 2025 15:45:21 -0700 Subject: [PATCH 36/39] fix typo --- bittensor/core/extrinsics/asyncex/sudo.py | 2 +- bittensor/core/extrinsics/sudo.py | 2 +- tests/e2e_tests/test_commit_reveal.py | 8 ++++---- tests/e2e_tests/test_commit_weights.py | 6 +++--- tests/e2e_tests/test_hotkeys.py | 4 ++-- tests/e2e_tests/test_set_weights.py | 4 ++-- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/sudo.py b/bittensor/core/extrinsics/asyncex/sudo.py index 61d961d742..b61cb10bfc 100644 --- a/bittensor/core/extrinsics/asyncex/sudo.py +++ b/bittensor/core/extrinsics/asyncex/sudo.py @@ -9,7 +9,7 @@ from bittensor.core.async_subtensor import AsyncSubtensor -async def sudo_set_admin_freez_window_extrinsic( +async def sudo_set_admin_freeze_window_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", window: int, diff --git a/bittensor/core/extrinsics/sudo.py b/bittensor/core/extrinsics/sudo.py index 473a45bf55..9606446858 100644 --- a/bittensor/core/extrinsics/sudo.py +++ b/bittensor/core/extrinsics/sudo.py @@ -9,7 +9,7 @@ from bittensor.core.subtensor import Subtensor -def sudo_set_admin_freez_window_extrinsic( +def sudo_set_admin_freeze_window_extrinsic( subtensor: "Subtensor", wallet: "Wallet", window: int, diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index a45376e1f6..61f6bdeeed 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -7,11 +7,11 @@ from bittensor.core.extrinsics.asyncex.sudo import ( sudo_set_mechanism_count_extrinsic as async_sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freez_window_extrinsic as async_sudo_set_admin_freez_window_extrinsic, + sudo_set_admin_freeze_window_extrinsic as async_sudo_set_admin_freeze_window_extrinsic, ) from bittensor.core.extrinsics.sudo import ( sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freez_window_extrinsic, + sudo_set_admin_freeze_window_extrinsic, ) from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit @@ -46,7 +46,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0) + assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) # 12 for non-fast-block, 0.25 for fast block BLOCK_TIME, TEMPO_TO_SET = ( @@ -294,7 +294,7 @@ async def test_async_commit_and_reveal_weights_cr4( logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") # turn off admin freeze window limit for testing - assert await async_sudo_set_admin_freez_window_extrinsic( + assert await async_sudo_set_admin_freeze_window_extrinsic( async_subtensor, alice_wallet, 0 ) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index c3826febf1..4de52ecf45 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -4,7 +4,7 @@ from bittensor.core.extrinsics.sudo import ( sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freez_window_extrinsic, + sudo_set_admin_freeze_window_extrinsic, ) from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging @@ -35,7 +35,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa """ # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0), ( + assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" ) @@ -189,7 +189,7 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall """ # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0), ( + assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" ) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index adbb1fed9b..36d1bbfc4d 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -12,7 +12,7 @@ NonAssociatedColdKey, ) from bittensor.core.extrinsics.sudo import ( - sudo_set_admin_freez_window_extrinsic, + sudo_set_admin_freeze_window_extrinsic, ) from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import sudo_set_admin_utils @@ -89,7 +89,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w """ # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window_extrinsic(subtensor, alice_wallet, 0) + assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) dave_subnet_netuid = subtensor.get_total_subnets() # 2 set_tempo = 10 # affect to non-fast-blocks mode diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 29a9a1c2fc..47b50f8d64 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -4,7 +4,7 @@ from bittensor.core.extrinsics.sudo import ( sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freez_window_extrinsic, + sudo_set_admin_freeze_window_extrinsic, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -32,7 +32,7 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) AssertionError: If any of the checks or verifications fail """ # turn off admin freeze window limit for testing - assert sudo_set_admin_freez_window_extrinsic( + assert sudo_set_admin_freeze_window_extrinsic( subtensor=subtensor, wallet=alice_wallet, window=0, From 044723f30da8e9ef24ae37e9bf131112106df151 Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:44:02 -0700 Subject: [PATCH 37/39] Update bittensor/core/async_subtensor.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor/core/async_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 8bafa3f875..85b2325b42 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2146,7 +2146,7 @@ async def get_metagraph_info( params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) - if query is None or not hasattr(query, "value") or query.value is None: + if getattr(query, "value", None") is None: logging.error( f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) From 35caea069a7f456a9780d64dd3e5a6ec33dbeec5 Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Mon, 22 Sep 2025 12:44:46 -0700 Subject: [PATCH 38/39] Update bittensor/core/async_subtensor.py the same just more obvious, but longer Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor/core/async_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 85b2325b42..f693b93049 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2741,7 +2741,7 @@ async def get_mechanism_count( params=[netuid], block_hash=block_hash, ) - return query.value if query is not None and hasattr(query, "value") else 1 + return getattr(query, "value", 1) async def get_subnet_info( self, From 1c0ffd3e55967e1818e650b7831bdb13fdf65299 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 22 Sep 2025 12:55:31 -0700 Subject: [PATCH 39/39] review changes + ruff --- bittensor/core/async_subtensor.py | 4 ++-- bittensor/core/extrinsics/asyncex/children.py | 2 +- bittensor/core/extrinsics/children.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f693b93049..8c6545108d 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2146,7 +2146,7 @@ async def get_metagraph_info( params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, ) - if getattr(query, "value", None") is None: + if getattr(query, "value", None) is None: logging.error( f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) @@ -5210,7 +5210,7 @@ async def set_children( 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. + SubnetNotExists: 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. diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 46853642fe..8cfca27f78 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -44,7 +44,7 @@ async def set_children_extrinsic( 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. + SubnetNotExists: 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. diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index dd91fbe97b..993016e6ca 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -44,7 +44,7 @@ def set_children_extrinsic( 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. + SubnetNotExists: 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.