From 8e7489c35e85183ac68ad2fe0b576d064e3a6818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20=C5=BBy=C5=BAniewski?= Date: Tue, 4 Mar 2025 15:49:55 +0100 Subject: [PATCH] fix(2715): use ChainIdentity for identities --- bittensor/core/async_subtensor.py | 92 +++++------------ bittensor/core/settings.py | 3 - bittensor/core/subtensor.py | 87 +++++----------- bittensor/utils/__init__.py | 58 +---------- bittensor/utils/delegates_details.py | 44 -------- tests/e2e_tests/test_delegate.py | 62 +++++++----- tests/e2e_tests/test_hotkeys.py | 40 -------- tests/e2e_tests/utils/chain_interactions.py | 36 ------- tests/unit_tests/test_async_subtensor.py | 107 +++++++++++--------- 9 files changed, 147 insertions(+), 382 deletions(-) delete mode 100644 bittensor/utils/delegates_details.py diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 81304bbd29..02cc910f2a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5,7 +5,6 @@ from functools import partial from typing import Optional, Any, Union, Iterable, TYPE_CHECKING -import aiohttp import asyncstdlib as a import numpy as np import scalecodec @@ -28,6 +27,7 @@ decode_account_id, DynamicInfo, ) +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_metadata from bittensor.core.config import Config @@ -68,15 +68,14 @@ reveal_weights_extrinsic, ) from bittensor.core.metagraph import AsyncMetagraph -from bittensor.core.settings import version_as_int, TYPE_REGISTRY, DELEGATES_DETAILS_URL +from bittensor.core.settings import version_as_int, TYPE_REGISTRY from bittensor.core.types import ParamWithTypes, SubtensorMixin from bittensor.utils import ( + Certificate, decode_hex_identity_dict, format_error_message, torch, u16_normalized_float, - _decode_hex_identity_dict, - Certificate, u64_normalized_float, ) from bittensor.utils.balance import ( @@ -85,7 +84,6 @@ check_and_convert_to_balance, ) from bittensor.utils.btlogging import logging -from bittensor.utils.delegates_details import DelegatesDetails from bittensor.utils.weight_utils import generate_weight_hash if TYPE_CHECKING: @@ -1079,11 +1077,9 @@ async def get_delegate_identities( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, "DelegatesDetails"]: + ) -> dict[str, ChainIdentity]: """ - Fetches delegates identities from the chain and GitHub. Preference is given to chain data, and missing info is - filled-in by the info from GitHub. At some point, we want to totally move away from fetching this info from - GitHub, but chain data is still limited in that regard. + Fetches delegates identities from the chain. Arguments: block (Optional[int]): The blockchain block number for the query. @@ -1091,63 +1087,23 @@ async def get_delegate_identities( reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: - Dict {ss58: DelegatesDetails, ...} + Dict {ss58: ChainIdentity, ...} """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - timeout = aiohttp.ClientTimeout(10.0) - async with aiohttp.ClientSession(timeout=timeout) as session: - identities_info, response = await asyncio.gather( - self.substrate.query_map( - module="Registry", - storage_function="IdentityOf", - block_hash=block_hash, - reuse_block_hash=reuse_block, - ), - session.get(DELEGATES_DETAILS_URL), - ) - - all_delegates_details = {} - async for ss58_address, identity in identities_info: - all_delegates_details.update( - { - decode_account_id( - ss58_address[0] - ): DelegatesDetails.from_chain_data( - decode_hex_identity_dict(identity.value["info"]) - ) - } - ) - - if response.ok: - all_delegates: dict[str, Any] = await response.json(content_type=None) - - for delegate_hotkey, delegate_details in all_delegates.items(): - delegate_info = all_delegates_details.setdefault( - delegate_hotkey, - DelegatesDetails( - display=delegate_details.get("name", ""), - web=delegate_details.get("url", ""), - additional=delegate_details.get("description", ""), - pgp_fingerprint=delegate_details.get("fingerprint", ""), - ), - ) - delegate_info.display = ( - delegate_info.display or delegate_details.get("name", "") - ) - delegate_info.web = delegate_info.web or delegate_details.get( - "url", "" - ) - delegate_info.additional = ( - delegate_info.additional - or delegate_details.get("description", "") - ) - delegate_info.pgp_fingerprint = ( - delegate_info.pgp_fingerprint - or delegate_details.get("fingerprint", "") - ) + identities = await self.substrate.query_map( + module="SubtensorModule", + storage_function="IdentitiesV2", + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) - return all_delegates_details + return { + decode_account_id(ss58_address[0]): ChainIdentity.from_dict( + decode_hex_identity_dict(identity.value), + ) + async for ss58_address, identity in identities + } async def get_delegate_take( self, @@ -2424,7 +2380,7 @@ async def query_identity( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict: + ) -> Optional[ChainIdentity]: """ Queries the identity of a neuron on the Bittensor blockchain using the given key. This function retrieves detailed identity information about a specific neuron, which is a crucial aspect of the network's @@ -2455,12 +2411,16 @@ async def query_identity( block_hash=block_hash, reuse_block_hash=reuse_block, ) + if not identity_info: - return {} + return None + try: - return _decode_hex_identity_dict(identity_info) + return ChainIdentity.from_dict( + decode_hex_identity_dict(identity_info), + ) except TypeError: - return {} + return None async def recycle( self, diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index dcd5fcc8ca..f300dab47f 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -72,9 +72,6 @@ # Wallet ss58 address length SS58_ADDRESS_LENGTH = 48 -# Raw GitHub url for delegates registry file -DELEGATES_DETAILS_URL = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json" - # Block Explorers map network to explorer url # Must all be polkadotjs explorer urls NETWORK_EXPLORER_MAP = { diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ab7e2b5d53..406074bb12 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -5,12 +5,10 @@ from typing import TYPE_CHECKING, Any, Iterable, Optional, Union, cast import numpy as np -import requests import scalecodec from async_substrate_interface.errors import SubstrateRequestException from async_substrate_interface.types import ScaleObj from async_substrate_interface.sync_substrate import SubstrateInterface -from async_substrate_interface.utils import json from numpy.typing import NDArray from bittensor.core.async_subtensor import ProposalVoteData @@ -29,6 +27,7 @@ DelegatedInfo, decode_account_id, ) +from bittensor.core.chain_data.chain_identity import ChainIdentity 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 @@ -71,16 +70,14 @@ version_as_int, SS58_FORMAT, TYPE_REGISTRY, - DELEGATES_DETAILS_URL, ) from bittensor.core.types import ParamWithTypes, SubtensorMixin from bittensor.utils import ( - torch, - format_error_message, + Certificate, decode_hex_identity_dict, + format_error_message, + torch, u16_normalized_float, - _decode_hex_identity_dict, - Certificate, u64_normalized_float, ) from bittensor.utils.balance import ( @@ -90,7 +87,6 @@ check_and_convert_to_balance, ) from bittensor.utils.btlogging import logging -from bittensor.utils.delegates_details import DelegatesDetails from bittensor.utils.weight_utils import generate_weight_hash if TYPE_CHECKING: @@ -815,62 +811,29 @@ def get_delegate_by_hotkey( def get_delegate_identities( self, block: Optional[int] = None - ) -> dict[str, "DelegatesDetails"]: + ) -> dict[str, ChainIdentity]: """ - Fetches delegates identities from the chain and GitHub. Preference is given to chain data, and missing info is - filled-in by the info from GitHub. At some point, we want to totally move away from fetching this info from - GitHub, but chain data is still limited in that regard. + Fetches delegates identities from the chain. Arguments: block (Optional[int]): The blockchain block number for the query. Returns: - Dict {ss58: DelegatesDetails, ...} + Dict {ss58: ChainIdentity, ...} """ - block_hash = self.determine_block_hash(block) - response = requests.get(DELEGATES_DETAILS_URL) - identities_info = self.substrate.query_map( - module="Registry", storage_function="IdentityOf", block_hash=block_hash - ) - - all_delegates_details = {} - for ss58_address, identity in identities_info: - all_delegates_details.update( - { - decode_account_id( - ss58_address[0] - ): DelegatesDetails.from_chain_data( - decode_hex_identity_dict(identity.value["info"]) - ) - } - ) - if response.ok: - all_delegates: dict[str, Any] = json.loads(response.content) - - for delegate_hotkey, delegate_details in all_delegates.items(): - delegate_info = all_delegates_details.setdefault( - delegate_hotkey, - DelegatesDetails( - display=delegate_details.get("name", ""), - web=delegate_details.get("url", ""), - additional=delegate_details.get("description", ""), - pgp_fingerprint=delegate_details.get("fingerprint", ""), - ), - ) - delegate_info.display = delegate_info.display or delegate_details.get( - "name", "" - ) - delegate_info.web = delegate_info.web or delegate_details.get("url", "") - delegate_info.additional = ( - delegate_info.additional or delegate_details.get("description", "") - ) - delegate_info.pgp_fingerprint = ( - delegate_info.pgp_fingerprint - or delegate_details.get("fingerprint", "") - ) + identities = self.substrate.query_map( + module="SubtensorModule", + storage_function="IdentitiesV2", + block_hash=self.determine_block_hash(block), + ) - return all_delegates_details + return { + decode_account_id(ss58_address[0]): ChainIdentity.from_dict( + decode_hex_identity_dict(identity.value), + ) + for ss58_address, identity in identities + } def get_delegate_take( self, hotkey_ss58: str, block: Optional[int] = None @@ -1843,7 +1806,9 @@ def neurons_lite( return NeuronInfoLite.list_from_dicts(result) - def query_identity(self, coldkey_ss58: str, block: Optional[int] = None) -> dict: + def query_identity( + self, coldkey_ss58: str, block: Optional[int] = None + ) -> Optional[ChainIdentity]: """ Queries the identity of a neuron on the Bittensor blockchain using the given key. This function retrieves detailed identity information about a specific neuron, which is a crucial aspect of the network's @@ -1870,12 +1835,16 @@ def query_identity(self, coldkey_ss58: str, block: Optional[int] = None) -> dict params=[coldkey_ss58], block_hash=self.determine_block_hash(block), ) + if not identity_info: - return {} + return None + try: - return _decode_hex_identity_dict(identity_info) + return ChainIdentity.from_dict( + decode_hex_identity_dict(identity_info), + ) except TypeError: - return {} + return None def recycle(self, netuid: int, block: Optional[int] = None) -> Optional[Balance]: """ diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 5fef4cf172..c116cc0f84 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -50,8 +50,7 @@ def __new__(cls, data: Union[str, dict]): return str.__new__(cls, string) -def _decode_hex_identity_dict(info_dictionary: dict[str, Any]) -> dict[str, Any]: - # TODO why does this exist alongside `decode_hex_identity_dict`? +def decode_hex_identity_dict(info_dictionary: dict[str, Any]) -> dict[str, Any]: """Decodes a dictionary of hexadecimal identities.""" decoded_info = {} for k, v in info_dictionary.items(): @@ -317,61 +316,6 @@ def is_valid_bittensor_address_or_public_key(address: Union[str, bytes]) -> bool return False -def decode_hex_identity_dict(info_dictionary) -> dict[str, Any]: - """ - Decodes hex-encoded strings in a dictionary. - - This function traverses the given dictionary, identifies hex-encoded strings, and decodes them into readable - strings. It handles nested dictionaries and lists within the dictionary. - - Args: - info_dictionary (dict): The dictionary containing hex-encoded strings to decode. - - Returns: - dict: The dictionary with decoded strings. - - Examples: - input_dict = { - ... "name": {"value": "0x6a6f686e"}, - ... "additional": [ - ... [{"data": "0x64617461"}] - ... ] - ... } - decode_hex_identity_dict(input_dict) - {'name': 'john', 'additional': [('data', 'data')]} - """ - - def get_decoded(data: str) -> Optional[str]: - """Decodes a hex-encoded string.""" - try: - return bytes.fromhex(data[2:]).decode() - except UnicodeDecodeError: - print(f"Could not decode: {key}: {item}") - - for key, value in info_dictionary.items(): - if isinstance(value, dict): - item = list(value.values())[0] - if isinstance(item, str) and item.startswith("0x"): - try: - info_dictionary[key] = get_decoded(item) - except UnicodeDecodeError: - print(f"Could not decode: {key}: {item}") - else: - info_dictionary[key] = item - if key == "additional": - additional = [] - for item in value: - additional.append( - tuple( - get_decoded(data=next(iter(sub_item.values()))) - for sub_item in item - ) - ) - info_dictionary[key] = additional - - return info_dictionary - - def validate_chain_endpoint(endpoint_url: str) -> tuple[bool, str]: """Validates if the provided endpoint URL is a valid WebSocket URL.""" parsed = urlparse(endpoint_url) diff --git a/bittensor/utils/delegates_details.py b/bittensor/utils/delegates_details.py deleted file mode 100644 index 92f3872dd2..0000000000 --- a/bittensor/utils/delegates_details.py +++ /dev/null @@ -1,44 +0,0 @@ -from dataclasses import dataclass -from typing import Any, Optional, Union - - -# TODO: consider move it to `bittensor.core.chain_data` -@dataclass -class DelegatesDetails: - display: str - additional: list[tuple[str, str]] - web: str - legal: Optional[str] = None - riot: Optional[str] = None - email: Optional[str] = None - pgp_fingerprint: Optional[str] = None - image: Optional[str] = None - twitter: Optional[str] = None - - @classmethod - def from_chain_data(cls, data: dict[str, Any]) -> "DelegatesDetails": - def decode(key: str, default: Union[Optional[str], list] = ""): - try: - if isinstance(data.get(key), dict): - value = next(data.get(key).values()) - return bytes(value[0]).decode("utf-8") - elif isinstance(data.get(key), int): - return data.get(key) - elif isinstance(data.get(key), tuple): - return bytes(data.get(key)[0]).decode("utf-8") - else: - return default - except (UnicodeDecodeError, TypeError): - return default - - return cls( - display=decode("display"), - additional=decode("additional", []), - web=decode("web"), - legal=decode("legal"), - riot=decode("riot"), - email=decode("email"), - pgp_fingerprint=decode("pgp_fingerprint", None), - image=decode("image"), - twitter=decode("twitter"), - ) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 0bc980b281..ae9c6b9cac 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -1,12 +1,12 @@ import pytest +from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.delegate_info import DelegateInfo, DelegatedInfo from bittensor.utils.balance import Balance -from bittensor.utils.delegates_details import DelegatesDetails from tests.e2e_tests.utils.chain_interactions import ( decrease_take, increase_take, - registry_set_identity, + set_identity, sudo_set_admin_utils, ) @@ -21,18 +21,13 @@ def test_identity(subtensor, alice_wallet, bob_wallet): - Update Delegate's identity """ - identities = subtensor.get_delegate_identities() + identity = subtensor.query_identity(alice_wallet.coldkeypub.ss58_address) - assert alice_wallet.hotkey.ss58_address not in identities + assert identity is None - # Replace hotkey as it's the same as coldkey. - # It's required to edit identity later. - # Otherwise only subnet owner can do this. - alice_wallet.set_hotkey( - keypair=bob_wallet.hotkey, - encrypt=False, - overwrite=True, - ) + identities = subtensor.get_delegate_identities() + + assert alice_wallet.coldkey.ss58_address not in identities subtensor.root_register( alice_wallet, @@ -42,35 +37,46 @@ def test_identity(subtensor, alice_wallet, bob_wallet): identities = subtensor.get_delegate_identities() - assert alice_wallet.hotkey.ss58_address not in identities + assert alice_wallet.coldkey.ss58_address not in identities - success, error = registry_set_identity( + success, error = set_identity( subtensor, alice_wallet, - alice_wallet.hotkey.ss58_address, - display=b"Alice Display", - web=b"https://bittensor.com/", + name="Alice", + url="https://www.example.com", + github_repo="https://github.com/opentensor/bittensor", + description="Local Chain", ) assert error == "" assert success is True + identity = subtensor.query_identity(alice_wallet.coldkeypub.ss58_address) + + assert identity == ChainIdentity( + additional="", + description="Local Chain", + discord="", + github="https://github.com/opentensor/bittensor", + image="", + name="Alice", + url="https://www.example.com", + ) + identities = subtensor.get_delegate_identities() - assert alice_wallet.hotkey.ss58_address in identities + assert alice_wallet.coldkey.ss58_address in identities - alice_identity = identities[alice_wallet.hotkey.ss58_address] + identity = identities[alice_wallet.coldkey.ss58_address] - assert alice_identity == DelegatesDetails( - additional=[], - display="Alice Display", - email="", + assert identity == ChainIdentity( + additional="", + description="Local Chain", + discord="", + github="https://github.com/opentensor/bittensor", image="", - legal="", - pgp_fingerprint=None, - riot="", - twitter="", - web="https://bittensor.com/", + name="Alice", + url="https://www.example.com", ) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index a432aa702a..e8900e247f 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -2,7 +2,6 @@ from tests.e2e_tests.utils.chain_interactions import ( set_children, - set_identity, wait_epoch, ) @@ -10,45 +9,6 @@ SET_CHILDREN_RATE_LIMIT = 150 -def test_identity(subtensor, alice_wallet): - """ - Tests: - - Check default identity - - Update identity - """ - - coldkey = alice_wallet.coldkeypub.ss58_address - - assert subtensor.query_identity(coldkey) == {} - - subtensor.burned_register( - alice_wallet, - netuid=1, - ) - - success, error = set_identity( - subtensor, - alice_wallet, - name="Alice", - url="https://www.example.com", - github_repo="https://github.com/opentensor/bittensor", - description="Local Chain", - ) - - assert error == "" - assert success is True - - assert subtensor.query_identity(coldkey) == { - "name": "Alice", - "url": "https://www.example.com", - "github_repo": "https://github.com/opentensor/bittensor", - "image": "", - "discord": "", - "description": "Local Chain", - "additional": "", - } - - def test_hotkeys(subtensor, alice_wallet): """ Tests: diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index eda33d129a..b80548041b 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -269,42 +269,6 @@ def decrease_take(subtensor, wallet, take): ) -def registry_set_identity(subtensor, wallet, identified, **info): - DEFAULT = { - "additional": [[]], - "display": {"Raw0": ""}, - "legal": {"Raw0": ""}, - "web": {"Raw0": ""}, - "riot": {"Raw0": ""}, - "email": {"Raw0": ""}, - "pgp_fingerprint": None, - "image": {"Raw0": ""}, - "info": {"Raw0": ""}, - "twitter": {"Raw0": ""}, - } - - info = DEFAULT.copy() | { - key: { - f"Raw{len(value)}": value, - } - for key, value in info.items() - } - - return subtensor.sign_and_send_extrinsic( - subtensor.substrate.compose_call( - call_module="Registry", - call_function="set_identity", - call_params={ - "info": info, - "identified": identified, - }, - ), - wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - def set_identity( subtensor, wallet, diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index becc9776b8..be95739324 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -8,6 +8,7 @@ from bittensor import u64_normalized_float from bittensor.core import async_subtensor from bittensor.core.async_subtensor import AsyncSubtensor +from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.stake_info import StakeInfo from bittensor.core.chain_data import proposal_vote_data from bittensor.utils.balance import Balance @@ -56,7 +57,7 @@ def test_decode_ss58_tuples_in_proposal_vote_data(mocker): def test_decode_hex_identity_dict_with_non_tuple_value(): """Tests _decode_hex_identity_dict when value is not a tuple.""" info_dict = {"info": "regular_string"} - result = async_subtensor._decode_hex_identity_dict(info_dict) + result = async_subtensor.decode_hex_identity_dict(info_dict) assert result["info"] == "regular_string" @@ -1346,17 +1347,19 @@ async def test_query_identity_successful(subtensor, mocker): # Preps fake_coldkey_ss58 = "test_key" fake_block_hash = "block_hash" - fake_identity_info = {"info": {"stake": (b"\x01\x02",)}} + fake_identity_info = { + "additional": "Additional", + "description": "Description", + "discord": "", + "github_repo": "https://github.com/opentensor/bittensor", + "image": "", + "name": "Name", + "url": "https://www.example.com", + } mocked_query = mocker.AsyncMock(return_value=fake_identity_info) subtensor.substrate.query = mocked_query - mocker.patch.object( - async_subtensor, - "_decode_hex_identity_dict", - return_value={"stake": "01 02"}, - ) - # Call result = await subtensor.query_identity( coldkey_ss58=fake_coldkey_ss58, block_hash=fake_block_hash @@ -1370,7 +1373,15 @@ async def test_query_identity_successful(subtensor, mocker): block_hash=fake_block_hash, reuse_block_hash=False, ) - assert result == {"stake": "01 02"} + assert result == ChainIdentity( + additional="Additional", + description="Description", + discord="", + github="https://github.com/opentensor/bittensor", + image="", + name="Name", + url="https://www.example.com", + ) @pytest.mark.asyncio @@ -1393,7 +1404,7 @@ async def test_query_identity_no_info(subtensor, mocker): block_hash=None, reuse_block_hash=False, ) - assert result == {} + assert result is None @pytest.mark.asyncio @@ -1408,7 +1419,7 @@ async def test_query_identity_type_error(subtensor, mocker): mocker.patch.object( async_subtensor, - "_decode_hex_identity_dict", + "decode_hex_identity_dict", side_effect=TypeError, ) @@ -1423,7 +1434,7 @@ async def test_query_identity_type_error(subtensor, mocker): block_hash=None, reuse_block_hash=False, ) - assert result == {} + assert result is None @pytest.mark.asyncio @@ -2012,34 +2023,40 @@ async def test_get_delegate_identities(subtensor, mocker): """Tests get_delegate_identities with successful data retrieval from both chain and GitHub.""" # Preps fake_block_hash = "block_hash" - fake_chain_data = mocker.AsyncMock( - return_value=[ - ( - ["delegate1_ss58"], - mocker.Mock(value={"info": {"name": "Chain Delegate 1"}}), + fake_chain_data = [ + ( + ["delegate1_ss58"], + mocker.Mock( + value={ + "additional": "", + "description": "", + "discord": "", + "github_repo": "", + "image": "", + "name": "Chain Delegate 1", + "url": "", + }, ), - ( - ["delegate2_ss58"], - mocker.Mock(value={"info": {"name": "Chain Delegate 2"}}), + ), + ( + ["delegate2_ss58"], + mocker.Mock( + value={ + "additional": "", + "description": "", + "discord": "", + "github_repo": "", + "image": "", + "name": "Chain Delegate 2", + "url": "", + }, ), - ] - ) - fake_github_data = { - "delegate1_ss58": { - "name": "GitHub Delegate 1", - "url": "https://delegate1.com", - "description": "GitHub description 1", - "fingerprint": "fingerprint1", - }, - "delegate3_ss58": { - "name": "GitHub Delegate 3", - "url": "https://delegate3.com", - "description": "GitHub description 3", - "fingerprint": "fingerprint3", - }, - } + ), + ] - mocked_query_map = mocker.AsyncMock(return_value=fake_chain_data) + mocked_query_map = mocker.AsyncMock( + **{"return_value.__aiter__.return_value": iter(fake_chain_data)}, + ) subtensor.substrate.query_map = mocked_query_map mocked_decode_account_id = mocker.Mock(side_effect=lambda ss58: ss58) @@ -2050,27 +2067,19 @@ async def test_get_delegate_identities(subtensor, mocker): async_subtensor, "decode_hex_identity_dict", mocked_decode_hex_identity_dict ) - mock_response = mocker.Mock() - mock_response.ok = True - mock_response.json = mocker.AsyncMock(return_value=fake_github_data) - - mock_session_get = mocker.AsyncMock(return_value=mock_response) - mocker.patch("aiohttp.ClientSession.get", mock_session_get) - # Call result = await subtensor.get_delegate_identities(block_hash=fake_block_hash) # Asserts mocked_query_map.assert_called_once_with( - module="Registry", - storage_function="IdentityOf", + module="SubtensorModule", + storage_function="IdentitiesV2", block_hash=fake_block_hash, reuse_block_hash=False, ) - mock_session_get.assert_called_once_with(async_subtensor.DELEGATES_DETAILS_URL) - assert result["delegate1_ss58"].display == "GitHub Delegate 1" - assert result["delegate3_ss58"].display == "GitHub Delegate 3" + assert result["delegate1_ss58"].name == "Chain Delegate 1" + assert result["delegate2_ss58"].name == "Chain Delegate 2" @pytest.mark.asyncio