diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d0ca2de7ac..0c42aebd8a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -32,6 +32,7 @@ from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.delegate_info import DelegatedInfo from bittensor.core.chain_data.utils import ( + decode_block, decode_metadata, decode_revealed_commitment, decode_revealed_commitment_with_hotkey, @@ -59,6 +60,7 @@ root_register_extrinsic, ) from bittensor.core.extrinsics.asyncex.serving import ( + get_last_bonds_reset, publish_metadata, get_metadata, ) @@ -1171,6 +1173,34 @@ async def get_commitment( except TypeError: return "" + async def get_last_commitment_bonds_reset_block( + self, netuid: int, uid: int + ) -> Optional[int]: + """ + Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. + + Arguments: + netuid (int): The unique identifier of the subnetwork. + uid (int): The unique identifier of the neuron. + + Returns: + Optional[int]: The block number when the bonds were last reset, or None if not found. + """ + + metagraph = await self.metagraph(netuid) + try: + hotkey = metagraph.hotkeys[uid] + except IndexError: + logging.error( + "Your uid is not in the hotkeys. Please double-check your UID." + ) + return None + block = await get_last_bonds_reset(self, netuid, hotkey) + try: + return decode_block(block) + except TypeError: + return "" + async def get_all_commitments( self, netuid: int, diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index 9915b51c1f..58e35bba34 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -4,6 +4,7 @@ from typing import Optional, Union, TYPE_CHECKING from scalecodec.base import RuntimeConfiguration, ScaleBytes +from async_substrate_interface.types import ScaleObj from scalecodec.type_registry import load_type_registry_preset from scalecodec.utils.ss58 import ss58_encode @@ -140,6 +141,19 @@ def decode_metadata(metadata: dict) -> str: return bytes(bytes_tuple).decode() +def decode_block(data: bytes) -> int: + """ + Decode the block data from the given input if it is not None. + + Arguments: + data (bytes): The block data to decode. + + Returns: + int: The decoded block. + """ + return int(data.value) if isinstance(data, ScaleObj) else data + + def decode_revealed_commitment(encoded_data) -> tuple[int, str]: """ Decode the revealed commitment data from the given input if it is not None. diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 6fd5410838..5fa46554d0 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -236,6 +236,7 @@ async def publish_metadata( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, period: Optional[int] = None, + reset_bonds: bool = False, ) -> bool: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. @@ -256,6 +257,7 @@ async def publish_metadata( 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. + reset_bonds (bool): If `True`, the function will reset the bonds for the neuron. Defaults to `False`. Returns: bool: ``True`` if the metadata was successfully published (and finalized if specified). ``False`` otherwise. @@ -269,13 +271,17 @@ async def publish_metadata( logging.error(unlock.message) return False + fields = [{f"{data_type}": data}] + if reset_bonds: + fields.append({"ResetBondsFlag": b""}) + async with subtensor.substrate as substrate: call = await substrate.compose_call( call_module="Commitments", call_function="set_commitment", call_params={ "netuid": netuid, - "info": {"fields": [[{f"{data_type}": data}]]}, + "info": {"fields": [fields]}, }, ) @@ -314,3 +320,22 @@ async def get_metadata( reuse_block_hash=reuse_block, ) return commit_data + + +async def get_last_bonds_reset( + subtensor: "AsyncSubtensor", + netuid: int, + hotkey: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, +) -> bytes: + """Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid.""" + block_hash = await subtensor.determine_block_hash(block, block_hash, reuse_block) + block = await subtensor.substrate.query( + module="Commitments", + storage_function="LastBondsReset", + params=[netuid, hotkey], + block_hash=block_hash, + ) + return block diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 88da8997bc..09903d763e 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -233,6 +233,7 @@ def publish_metadata( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, period: Optional[int] = None, + reset_bonds: bool = False, ) -> bool: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. @@ -253,6 +254,7 @@ def publish_metadata( 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. + reset_bonds (bool): If `True`, the function will reset the bonds for the neuron. Defaults to `False`. Returns: bool: ``True`` if the metadata was successfully published (and finalized if specified). ``False`` otherwise. @@ -266,12 +268,16 @@ def publish_metadata( logging.error(unlock.message) return False + fields = [{f"{data_type}": data}] + if reset_bonds: + fields.append({"ResetBondsFlag": b""}) + call = subtensor.substrate.compose_call( call_module="Commitments", call_function="set_commitment", call_params={ "netuid": netuid, - "info": {"fields": [[{f"{data_type}": data}]]}, + "info": {"fields": [fields]}, }, ) @@ -300,3 +306,26 @@ def get_metadata( block_hash=subtensor.determine_block_hash(block), ) return commit_data + + +def get_last_bonds_reset( + subtensor: "Subtensor", netuid: int, hotkey: str, block: Optional[int] = None +) -> bytes: + """ + Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. + + Args: + subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance object. + netuid (int): The network uid to fetch from. + hotkey (str): The hotkey of the neuron for which to fetch the last bonds reset. + block (Optional[int]): The block number to query. If ``None``, the latest block is used. + + Returns: + bytes: The last bonds reset data for the specified hotkey and netuid. + """ + return subtensor.substrate.query( + module="Commitments", + storage_function="LastBondsReset", + params=[netuid, hotkey], + block_hash=subtensor.determine_block_hash(block), + ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index be9b18e9f4..eb608ab87f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -30,6 +30,7 @@ ) from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.utils import ( + decode_block, decode_metadata, decode_revealed_commitment, decode_revealed_commitment_with_hotkey, @@ -61,6 +62,7 @@ set_root_weights_extrinsic, ) from bittensor.core.extrinsics.serving import ( + get_last_bonds_reset, publish_metadata, get_metadata, serve_axon_extrinsic, @@ -903,6 +905,33 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> except TypeError: return "" + def get_last_commitment_bonds_reset_block( + self, netuid: int, uid: int + ) -> Optional[int]: + """ + Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. + + Arguments: + netuid (int): The unique identifier of the subnetwork. + uid (int): The unique identifier of the neuron. + + Returns: + Optional[int]: The block number when the bonds were last reset, or None if not found. + """ + + metagraph = self.metagraph(netuid) + try: + hotkey = metagraph.hotkeys[uid] + except IndexError: + logging.error( + "Your uid is not in the hotkeys. Please double-check your UID." + ) + return None + block = get_last_bonds_reset(self, netuid, hotkey) + if block is None: + return None + return decode_block(block) + def get_all_commitments( self, netuid: int, block: Optional[int] = None ) -> dict[str, str]: diff --git a/bittensor/core/subtensor_api/commitments.py b/bittensor/core/subtensor_api/commitments.py index 2e594ba6db..6bdbb7f9f3 100644 --- a/bittensor/core/subtensor_api/commitments.py +++ b/bittensor/core/subtensor_api/commitments.py @@ -12,6 +12,9 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_all_revealed_commitments = subtensor.get_all_revealed_commitments self.get_commitment = subtensor.get_commitment self.get_current_weight_commit_info = subtensor.get_current_weight_commit_info + self.get_last_commitment_bonds_reset_block = ( + subtensor.get_last_commitment_bonds_reset_block + ) self.get_revealed_commitment = subtensor.get_revealed_commitment self.get_revealed_commitment_by_hotkey = ( subtensor.get_revealed_commitment_by_hotkey diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index f0f2ded013..30afcce7f5 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -41,6 +41,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_children_pending = subtensor._subtensor.get_children_pending subtensor.get_commitment = subtensor._subtensor.get_commitment subtensor.get_current_block = subtensor._subtensor.get_current_block + subtensor.get_last_commitment_bonds_reset_block = ( + subtensor._subtensor.get_last_commitment_bonds_reset_block + ) subtensor.get_current_weight_commit_info = ( subtensor._subtensor.get_current_weight_commit_info ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 0d42b655bc..0704fc16ab 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1813,6 +1813,33 @@ def test_get_commitment(subtensor, mocker): assert result == expected_result +def test_get_last_commitment_bonds_reset_block(subtensor, mocker): + """Successful get_last_commitment_bonds_reset_block call.""" + # Preps + fake_netuid = 1 + fake_uid = 2 + fake_hotkey = "hotkey" + expected_result = 3 + + mocked_get_last_bonds_reset = mocker.patch.object( + subtensor_module, "get_last_bonds_reset" + ) + mocked_get_last_bonds_reset.return_value = expected_result + + mocked_metagraph = mocker.MagicMock() + subtensor.metagraph = mocked_metagraph + mocked_metagraph.return_value.hotkeys = {fake_uid: fake_hotkey} + + # Call + result = subtensor.get_last_commitment_bonds_reset_block( + netuid=fake_netuid, uid=fake_uid + ) + + # Assertions + mocked_get_last_bonds_reset.assert_called_once() + assert result == expected_result + + def test_min_allowed_weights(subtensor, mocker): """Successful min_allowed_weights call.""" fake_netuid = 1