diff --git a/CHANGELOG.md b/CHANGELOG.md index 08be696813..76fd44d797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 9.0.0rc4 /2025-02-07 +* Fix for extra fields from chain data by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2647 +* Adds get_all_commitments and fixes commitment tests and query_map @thewhaleking in https://github.com/opentensor/bittensor/pull/2644 + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.0.0rc2...v9.0.0rc3 + ## 9.0.0rc3 /2025-02-06 ## What's Changed diff --git a/VERSION b/VERSION index 5c9df55c0d..994a35715d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.0.0rc3 \ No newline at end of file +9.0.0rc4 \ No newline at end of file diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5b0cdd2db1..926c593ac0 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -26,6 +26,7 @@ decode_account_id, DynamicInfo, ) +from bittensor.core.chain_data.utils import decode_metadata from bittensor.core.config import Config from bittensor.core.errors import SubstrateRequestException from bittensor.core.extrinsics.asyncex.commit_reveal import commit_reveal_v3_extrinsic @@ -342,7 +343,7 @@ async def query_map( block_hash=block_hash, reuse_block_hash=reuse_block, ) - return getattr(result, "value", None) + return result async def query_map_subtensor( self, @@ -927,7 +928,12 @@ async def get_children( return False, [], format_error_message(e) async def get_commitment( - self, netuid: int, uid: int, block: Optional[int] = None + self, + netuid: int, + uid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> str: """ Retrieves the on-chain commitment for a specific neuron in the Bittensor network. @@ -937,6 +943,8 @@ async def get_commitment( uid (int): The unique identifier of the neuron. block (Optional[int]): The block number to retrieve the commitment from. If None, the latest block is used. Default is ``None``. + block_hash (Optional[str]): The hash of the block to retrieve the subnet unique identifiers from. + reuse_block (bool): Whether to reuse the last-used block hash. Returns: str: The commitment data as a string. @@ -950,15 +958,47 @@ async def get_commitment( ) return "" - metadata = await get_metadata(self, netuid, hotkey, block) + metadata = await get_metadata( + self, netuid, hotkey, block, block_hash, reuse_block + ) try: - commitment = metadata["info"]["fields"][0] # type: ignore - hex_data = commitment[list(commitment.keys())[0]][2:] # type: ignore - return bytes.fromhex(hex_data).decode() - + return decode_metadata(metadata) except TypeError: return "" + async def get_all_commitments( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[str, str]: + """ + Retrieves the on-chain commitments for a specific subnet in the Bittensor network. + + Arguments: + netuid (int): The unique identifier of the subnetwork. + block (Optional[int]): The block number to retrieve the commitment from. If None, the latest block is used. + Default is ``None``. + block_hash (Optional[str]): The hash of the block to retrieve the subnet unique identifiers from. + reuse_block (bool): Whether to reuse the last-used block hash. + + Returns: + dict[str, str]: A mapping of the ss58:commitment with the commitment as a string + """ + query = await self.query_map( + module="Commitments", + name="CommitmentOf", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + result = {} + async for id_, value in query: + result[decode_account_id(id_[0])] = decode_account_id(value) + return result + async def get_current_weight_commit_info( self, netuid: int, diff --git a/bittensor/core/chain_data/info_base.py b/bittensor/core/chain_data/info_base.py index 1b17f068b3..2912bde68e 100644 --- a/bittensor/core/chain_data/info_base.py +++ b/bittensor/core/chain_data/info_base.py @@ -1,4 +1,4 @@ -from dataclasses import dataclass +from dataclasses import dataclass, fields from typing import Any, TypeVar from bittensor.core.errors import SubstrateRequestException @@ -13,7 +13,13 @@ class InfoBase: @classmethod def from_dict(cls, decoded: dict) -> T: try: - return cls._from_dict(decoded) + class_fields = {f.name for f in fields(cls)} + extra_keys = decoded.keys() - class_fields + instance = cls._from_dict( + {k: v for k, v in decoded.items() if k in class_fields} + ) + [setattr(instance, k, decoded[k]) for k in extra_keys] + return instance except KeyError as e: raise SubstrateRequestException( f"The {cls} structure is missing {e} from the chain.", diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index 580d0b7fa2..67aabd7c57 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -7,7 +7,7 @@ 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.balance import Balance +from bittensor.utils.balance import Balance, fixed_to_float # to balance with unit (just shortcut) @@ -68,6 +68,7 @@ class MetagraphInfo(InfoBase): pending_alpha_emission: Balance # pending alpha to be distributed pending_root_emission: Balance # pending tao for root divs to be distributed subnet_volume: Balance # volume of the subnet in TAO + moving_price: Balance # subnet moving price. # Hparams for epoch rho: int # subnet rho param @@ -168,6 +169,9 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": ) decoded["pending_root_emission"] = _tbwu(decoded["pending_root_emission"]) decoded["subnet_volume"] = _tbwu(decoded["subnet_volume"], _netuid) + decoded["moving_price"] = Balance.from_tao( + fixed_to_float(decoded.get("moving_price"), 32) + ) # Hparams for epoch decoded["kappa"] = u16tf(decoded["kappa"]) @@ -243,6 +247,7 @@ class MetagraphInfoPool: alpha_in: float tao_in: float subnet_volume: float + moving_price: float @dataclass diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index bbd0e6c9ed..c7d6986038 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -129,3 +129,9 @@ def process_stake_data(stake_data: list) -> dict: account_id = decode_account_id(account_id_bytes) decoded_stake_data.update({account_id: Balance.from_rao(stake_)}) return decoded_stake_data + + +def decode_metadata(metadata: dict) -> str: + commitment = metadata["info"]["fields"][0][0] + bytes_tuple = commitment[next(iter(commitment.keys()))][0] + return bytes(bytes_tuple).decode() diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 4dd4417f99..5b38dafb56 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -306,4 +306,4 @@ async def get_metadata( block_hash=block_hash, reuse_block_hash=reuse_block, ) - return getattr(commit_data, "value", None) + return commit_data diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 2dff1e61f2..c5e3f96ee5 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -283,7 +283,7 @@ def publish_metadata( def get_metadata( subtensor: "Subtensor", netuid: int, hotkey: str, block: Optional[int] = None -) -> str: +) -> bytes: """Fetches metadata from the blockchain for a given hotkey and netuid.""" commit_data = subtensor.substrate.query( module="Commitments", @@ -291,4 +291,4 @@ def get_metadata( params=[netuid, hotkey], block_hash=subtensor.determine_block_hash(block), ) - return getattr(commit_data, "value", None) + return commit_data diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index accec5668d..a57a9e4507 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -989,6 +989,7 @@ def _apply_metagraph_info_mixin(self, metagraph_info: "MetagraphInfo"): alpha_in=metagraph_info.alpha_in.tao, tao_in=metagraph_info.tao_in.tao, subnet_volume=metagraph_info.subnet_volume.tao, + moving_price=metagraph_info.moving_price.tao, ) self.emissions = MetagraphInfoEmissions( alpha_out_emission=metagraph_info.alpha_out_emission.tao, diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 1b110cdabd..df8e9f4146 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -1,4 +1,4 @@ -__version__ = "9.0.0rc3" +__version__ = "9.0.0rc4" import os import re diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e48f5c967e..690af504b7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -24,6 +24,7 @@ SubnetInfo, decode_account_id, ) +from bittensor.core.chain_data.utils import decode_metadata from bittensor.core.config import Config from bittensor.core.extrinsics.commit_reveal import commit_reveal_v3_extrinsic from bittensor.core.extrinsics.commit_weights import ( @@ -210,7 +211,7 @@ def query_map( params=params, block_hash=self.determine_block_hash(block=block), ) - return getattr(result, "value", None) + return result def query_map_subtensor( self, name: str, block: Optional[int] = None, params: Optional[list] = None @@ -731,15 +732,27 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> ) return "" - metadata = get_metadata(self, netuid, hotkey, block) + metadata = cast(dict, get_metadata(self, netuid, hotkey, block)) try: - commitment = metadata["info"]["fields"][0] # type: ignore - hex_data = commitment[list(commitment.keys())[0]][2:] # type: ignore - return bytes.fromhex(hex_data).decode() + return decode_metadata(metadata) except TypeError: return "" + def get_all_commitments( + self, netuid: int, block: Optional[int] = None + ) -> dict[str, str]: + query = self.query_map( + module="Commitments", + name="CommitmentOf", + params=[netuid], + block=block, + ) + result = {} + for id_, value in query: + result[decode_account_id(id_[0])] = decode_account_id(value) + return result + def get_current_weight_commit_info( self, netuid: int, block: Optional[int] = None ) -> list: diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index c49be249ed..29ce2faeaa 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -285,16 +285,18 @@ class FixedPoint(TypedDict): bits: int -def fixed_to_float(fixed: Union[FixedPoint, ScaleType]) -> float: - # Currently this is stored as a U64F64 +def fixed_to_float( + fixed: Union[FixedPoint, ScaleType], frac_bits: int = 64, total_bits: int = 128 +) -> float: + # By default, this is a U64F64 # which is 64 bits of integer and 64 bits of fractional - frac_bits = 64 data: int = fixed["bits"] - # Shift bits to extract integer part (assuming 64 bits for integer part) - integer_part = data >> frac_bits + # Logical and to get the fractional part; remaining is the integer part fractional_part = data & (2**frac_bits - 1) + # Shift to get the integer part from the remaining bits + integer_part = data >> (total_bits - frac_bits) frac_float = fractional_part / (2**frac_bits) diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index fac094b26e..7ed3c6efa8 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -7,7 +7,7 @@ from bittensor.core.chain_data.axon_info import AxonInfo from bittensor.core.subtensor import Subtensor from bittensor.utils.balance import Balance -from tests.helpers.helpers import FakeConnectContextManager +from tests.helpers.helpers import FakeWebsocket @pytest.fixture @@ -29,12 +29,12 @@ async def prepare_test(mocker, seed): ) as f: metadata_v15 = MetadataV15.decode_from_metadata_option(f.read()) registry = PortableRegistry.from_metadata_v15(metadata_v15) - subtensor = Subtensor("unknown", _mock=True) - subtensor.substrate.metadata_v15 = metadata_v15 mocker.patch( "async_substrate_interface.sync_substrate.connect", - mocker.Mock(return_value=FakeConnectContextManager(seed=seed)), + mocker.Mock(return_value=FakeWebsocket(seed=seed)), ) + subtensor = Subtensor("unknown", _mock=True) + subtensor.substrate.metadata_v15 = metadata_v15 mocker.patch.object(subtensor.substrate, "registry", registry) return subtensor diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index d7ec8d9b35..c11ac36956 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1000,7 +1000,7 @@ def test_query_map(subtensor, mocker): params=None, block_hash=None, ) - assert result == subtensor.substrate.query_map.return_value.value + assert result == subtensor.substrate.query_map.return_value def test_query_constant(subtensor, mocker): @@ -1682,8 +1682,10 @@ def test_get_commitment(subtensor, mocker): fake_uid = 2 fake_block = 3 fake_hotkey = "hotkey" - fake_hex_data = "0x010203" - expected_result = bytes.fromhex(fake_hex_data[2:]).decode() + expected_result = ( + "{'peer_id': '12D3KooWFWnHBmUFxvfL6PfZ5eGHdhgsEqNnsxuN1HE9EtfW8THi', " + "'model_huggingface_id': 'kmfoda/gpt2-1b-miner-3'}" + ) mocked_metagraph = mocker.MagicMock() subtensor.metagraph = mocked_metagraph @@ -1691,7 +1693,137 @@ def test_get_commitment(subtensor, mocker): mocked_get_metadata = mocker.patch.object(subtensor_module, "get_metadata") mocked_get_metadata.return_value = { - "info": {"fields": [{fake_hex_data: fake_hex_data}]} + "deposit": 0, + "block": 3843930, + "info": { + "fields": ( + ( + { + "Raw117": ( + ( + 123, + 39, + 112, + 101, + 101, + 114, + 95, + 105, + 100, + 39, + 58, + 32, + 39, + 49, + 50, + 68, + 51, + 75, + 111, + 111, + 87, + 70, + 87, + 110, + 72, + 66, + 109, + 85, + 70, + 120, + 118, + 102, + 76, + 54, + 80, + 102, + 90, + 53, + 101, + 71, + 72, + 100, + 104, + 103, + 115, + 69, + 113, + 78, + 110, + 115, + 120, + 117, + 78, + 49, + 72, + 69, + 57, + 69, + 116, + 102, + 87, + 56, + 84, + 72, + 105, + 39, + 44, + 32, + 39, + 109, + 111, + 100, + 101, + 108, + 95, + 104, + 117, + 103, + 103, + 105, + 110, + 103, + 102, + 97, + 99, + 101, + 95, + 105, + 100, + 39, + 58, + 32, + 39, + 107, + 109, + 102, + 111, + 100, + 97, + 47, + 103, + 112, + 116, + 50, + 45, + 49, + 98, + 45, + 109, + 105, + 110, + 101, + 114, + 45, + 51, + 39, + 125, + ), + ) + }, + ), + ) + }, } # Call