From 3c1a1de73f3bd79ade3dd62d1e85930b7676f54b Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 13:20:50 +0200 Subject: [PATCH 01/38] Add reqs --- pyproject.toml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d244bea20e..e98ce292fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,11 +29,12 @@ dependencies = [ "retry==0.9.2", "requests>=2.0.0,<3.0", "pydantic>=2.3,<3", - "scalecodec==1.2.12", + "cyscale==0.1.11", "uvicorn", "bittensor-drand>=1.3.0,<2.0.0", "bittensor-wallet==4.0.1", - "async-substrate-interface>=1.6.2,<2.0.0" + # TODO this will need to be changed to asi 2.0-3.0 when merged + "async-substrate-interface @ git+https://github.com/opentensor/async-substrate-interface.git@release/2.0.0", ] [project.optional-dependencies] From 73fba4a7acd4e4343ad91b2a02be0e40b7d20eae Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 13:48:19 +0200 Subject: [PATCH 02/38] Remove ScaleObj --- bittensor/core/async_subtensor.py | 27 ++++++---------- bittensor/core/chain_data/coldkey_swap.py | 8 ++--- bittensor/core/chain_data/utils.py | 6 ++-- bittensor/core/subtensor.py | 32 ++++++++----------- bittensor/utils/balance.py | 22 +------------ .../chain_data/test_coldkey_swap.py | 8 ++--- tests/unit_tests/test_async_subtensor.py | 7 ++-- tests/unit_tests/test_subtensor.py | 7 ++-- 8 files changed, 45 insertions(+), 72 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 0f22901573..d142a71473 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -8,11 +8,11 @@ import scalecodec from async_substrate_interface import AsyncSubstrateInterface from async_substrate_interface.substrate_addons import RetryAsyncSubstrate -from async_substrate_interface.types import ScaleObj from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT from scalecodec import GenericCall +from scalecodec.base import ScaleType from bittensor.core.chain_data import ( ColdkeySwapAnnouncementInfo, @@ -43,7 +43,6 @@ 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, @@ -755,7 +754,7 @@ async def query_constant( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional["ScaleObj"]: + ) -> Optional[ScaleType[Any]]: """Retrieves a constant from the specified module on the Bittensor blockchain. Use this function for nonstandard queries to constants defined within the Bittensor blockchain, if these cannot @@ -859,7 +858,7 @@ async def query_module( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[Union["ScaleObj", Any]]: + ) -> Optional[ScaleType[Any]]: """Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This function is a generic query interface that allows for flexible and diverse data retrieval from various blockchain modules. Use this function for nonstandard queries to storage defined within the Bittensor @@ -928,7 +927,7 @@ async def query_subtensor( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[Union["ScaleObj", Any]]: + ) -> Optional[ScaleType[Any]]: """Queries named storage from the Subtensor module on the Bittensor blockchain. Use this function for nonstandard queries to storage defined within the Bittensor blockchain, if these cannot @@ -1986,7 +1985,7 @@ async def get_coldkey_swap_announcement( if query is None: return None return ColdkeySwapAnnouncementInfo.from_query( - coldkey_ss58=coldkey_ss58, query=cast(ScaleObj, query) + coldkey_ss58=coldkey_ss58, query=query ) async def get_coldkey_swap_announcements( @@ -2130,9 +2129,7 @@ async def get_coldkey_swap_dispute( ) if query is None: return None - return ColdkeySwapDisputeInfo.from_query( - coldkey_ss58=coldkey_ss58, query=cast(ScaleObj, query) - ) + return ColdkeySwapDisputeInfo.from_query(coldkey_ss58=coldkey_ss58, query=query) async def get_coldkey_swap_disputes( self, @@ -2851,7 +2848,7 @@ async def get_last_bonds_reset( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ): + ) -> ScaleType[int]: """Retrieves the block number when bonds were last reset for a specific hotkey on a subnet. Parameters: @@ -2911,10 +2908,7 @@ async def get_last_commitment_bonds_reset_block( block_data = await self.get_last_bonds_reset( netuid, hotkey, block, block_hash, reuse_block ) - try: - return decode_block(block_data) - except TypeError: - return None + return getattr(block_data, "value", None) async def get_liquidity_list( self, @@ -5220,9 +5214,8 @@ async def is_fast_blocks(self) -> bool: - """ - slot_duration_obj = cast( - ScaleObj, await self.query_constant("Aura", "SlotDuration") - ) + slot_duration_obj = await self.query_constant("Aura", "SlotDuration") + assert slot_duration_obj is not None return slot_duration_obj.value == 250 async def is_hotkey_delegate( diff --git a/bittensor/core/chain_data/coldkey_swap.py b/bittensor/core/chain_data/coldkey_swap.py index e638569e53..c323a84a55 100644 --- a/bittensor/core/chain_data/coldkey_swap.py +++ b/bittensor/core/chain_data/coldkey_swap.py @@ -1,8 +1,6 @@ from dataclasses import asdict, dataclass, fields from typing import Optional - -from async_substrate_interface.types import ScaleObj - +from scalecodec.base import ScaleType from bittensor.core.chain_data.utils import decode_account_id @@ -34,7 +32,7 @@ class ColdkeySwapAnnouncementInfo: @classmethod def from_query( - cls, coldkey_ss58: str, query: "ScaleObj" + cls, coldkey_ss58: str, query: ScaleType ) -> Optional["ColdkeySwapAnnouncementInfo"]: """ Creates a ColdkeySwapAnnouncementInfo object from a Substrate query result. @@ -98,7 +96,7 @@ class ColdkeySwapDisputeInfo: @classmethod def from_query( - cls, coldkey_ss58: str, query: "ScaleObj" + cls, coldkey_ss58: str, query: ScaleType ) -> Optional["ColdkeySwapDisputeInfo"]: """ Creates a ColdkeySwapDisputeInfo object from a Substrate query result. diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index 5374652d8c..d6946c96ae 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -3,9 +3,9 @@ from enum import Enum from typing import Optional, Union, TYPE_CHECKING -from async_substrate_interface.types import ScaleObj from bittensor_wallet.utils import SS58_FORMAT -from scalecodec.base import RuntimeConfiguration, ScaleBytes +from scalecodec import ScaleBytes +from scalecodec.base import RuntimeConfiguration, ScaleType from scalecodec.type_registry import load_type_registry_preset from scalecodec.utils.ss58 import ss58_encode @@ -152,7 +152,7 @@ def decode_block(data: bytes) -> int: Returns: int: The decoded block. """ - return int(data.value) if isinstance(data, ScaleObj) else data + return int.from_bytes(data, byteorder="little") # TODO verify this is little endian def decode_revealed_commitment(encoded_data) -> tuple[int, str]: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 35a8818833..73c2299835 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -7,10 +7,10 @@ from async_substrate_interface.errors import SubstrateRequestException from async_substrate_interface.substrate_addons import RetrySyncSubstrate from async_substrate_interface.sync_substrate import SubstrateInterface -from async_substrate_interface.types import ScaleObj from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT +from scalecodec.base import ScaleType from bittensor.core.axon import Axon from bittensor.core.chain_data import ( @@ -42,7 +42,6 @@ ) 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, @@ -638,7 +637,7 @@ def sim_swap( def query_constant( self, module_name: str, constant_name: str, block: Optional[int] = None - ) -> Optional["ScaleObj"]: + ) -> Optional[ScaleType[Any]]: """Retrieves a constant from the specified module on the Bittensor blockchain. Use this function for nonstandard queries to constants defined within the Bittensor blockchain, if these cannot @@ -721,7 +720,7 @@ def query_module( name: str, params: Optional[list] = None, block: Optional[int] = None, - ) -> Optional[Union["ScaleObj", Any, FixedPoint]]: + ) -> Optional[ScaleType]: """Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This function is a generic query interface that allows for flexible and diverse data retrieval from various blockchain modules. Use this function for nonstandard queries to storage defined within the Bittensor @@ -775,7 +774,7 @@ def query_subtensor( name: str, params: Optional[list] = None, block: Optional[int] = None, - ) -> Optional[Union["ScaleObj", Any]]: + ) -> Optional[ScaleType]: """Queries named storage from the Subtensor module on the Bittensor blockchain. Use this function for nonstandard queries to constants defined within the Bittensor blockchain, if these cannot @@ -1604,7 +1603,7 @@ def get_coldkey_swap_announcement( if query is None: return None return ColdkeySwapAnnouncementInfo.from_query( - coldkey_ss58=coldkey_ss58, query=cast(ScaleObj, query) + coldkey_ss58=coldkey_ss58, query=cast(ScaleType, query) ) def get_coldkey_swap_announcements( @@ -1771,9 +1770,7 @@ def get_coldkey_swap_dispute( ) if query is None: return None - return ColdkeySwapDisputeInfo.from_query( - coldkey_ss58=coldkey_ss58, query=cast(ScaleObj, query) - ) + return ColdkeySwapDisputeInfo.from_query(coldkey_ss58=coldkey_ss58, query=query) def get_coldkey_swap_disputes( self, @@ -2350,10 +2347,7 @@ def get_last_commitment_bonds_reset_block( ) return None block_data = self.get_last_bonds_reset(netuid, hotkey_ss58, block) - try: - return decode_block(block_data) - except TypeError: - return None + return getattr(block_data, "value", None) def get_liquidity_list( self, @@ -3588,8 +3582,8 @@ def get_stake_for_hotkey( hotkey_alpha_query = self.query_subtensor( name="TotalHotkeyAlpha", params=[hotkey_ss58, netuid], block=block ) - hotkey_alpha = cast(ScaleObj, hotkey_alpha_query) - balance = Balance.from_rao(hotkey_alpha.value) + assert hotkey_alpha_query is not None + balance = Balance.from_rao(hotkey_alpha_query.value) balance.set_unit(netuid=netuid) return balance @@ -3969,8 +3963,9 @@ def get_timestamp(self, block: Optional[int] = None) -> datetime: Returns: datetime object for the timestamp of the block """ - unix = cast(ScaleObj, self.query_module("Timestamp", "Now", block=block)).value - return datetime.fromtimestamp(unix / 1000, tz=timezone.utc) + unix = self.query_module("Timestamp", "Now", block=block) + assert unix is not None + return datetime.fromtimestamp(unix.value / 1000, tz=timezone.utc) def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]: """Retrieves the total number of subnets within the Bittensor network as of a specific blockchain block. @@ -4254,7 +4249,8 @@ def is_fast_blocks(self) -> bool: - """ - slot_duration_obj = cast(ScaleObj, self.query_constant("Aura", "SlotDuration")) + slot_duration_obj = self.query_constant("Aura", "SlotDuration") + assert slot_duration_obj is not None return slot_duration_obj.value == 250 def is_hotkey_delegate(self, hotkey_ss58: str, block: Optional[int] = None) -> bool: diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 670174f48e..0fc55b4d5e 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,7 +1,7 @@ from typing import Optional, TypedDict, Union from scalecodec import ScaleType -from async_substrate_interface.types import ScaleObj +from scalecodec.utils.math import fixed_to_float as fixed_to_float from bittensor.core import settings from bittensor.core.errors import BalanceTypeError, BalanceUnitMismatchError @@ -374,26 +374,6 @@ class FixedPoint(TypedDict): bits: int -def fixed_to_float( - fixed: FixedPoint | ScaleType | ScaleObj, frac_bits: int = 64, total_bits: int = 128 -) -> float: - """Converts a fixed-point value (e.g., U64F64) into a floating-point number.""" - # By default, this is a U64F64 - # which is 64 bits of integer and 64 bits of fractional - data: int = ( - fb.value if isinstance((fb := fixed["bits"]), (ScaleType, ScaleObj)) else fb - ) - - # 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) - - return integer_part + frac_float - - # lowercase is added for backwards compatibility to not break API units = UNITS = [ chr( diff --git a/tests/unit_tests/chain_data/test_coldkey_swap.py b/tests/unit_tests/chain_data/test_coldkey_swap.py index e13e245af6..157f23b799 100644 --- a/tests/unit_tests/chain_data/test_coldkey_swap.py +++ b/tests/unit_tests/chain_data/test_coldkey_swap.py @@ -1,4 +1,4 @@ -from async_substrate_interface.types import ScaleObj +from scalecodec.base import ScaleType from bittensor.core.chain_data.coldkey_swap import ( ColdkeySwapAnnouncementInfo, @@ -10,7 +10,7 @@ def test_coldkey_swap_announcement_info_from_query_none(mocker): """Test from_query returns None when query has no value.""" # Prep coldkey_ss58 = mocker.Mock(spec=str) - query = mocker.Mock(spec=ScaleObj) + query = mocker.Mock(spec=ScaleType) # Call from_query = ColdkeySwapAnnouncementInfo.from_query(coldkey_ss58, query) @@ -46,7 +46,7 @@ def test_coldkey_swap_announcement_info_from_query_happy_path(mocker): def test_coldkey_swap_dispute_info_from_query_none(mocker): """Test from_query returns None when query has no value.""" coldkey_ss58 = mocker.Mock(spec=str) - query = mocker.Mock(spec=ScaleObj) + query = mocker.Mock(spec=ScaleType) query.value = None from_query = ColdkeySwapDisputeInfo.from_query(coldkey_ss58, query) @@ -58,7 +58,7 @@ def test_coldkey_swap_dispute_info_from_query_happy_path(mocker): """Test from_query returns ColdkeySwapDisputeInfo when query has valid data.""" coldkey_ss58 = mocker.Mock(spec=str) fake_block = 12345 - query = mocker.Mock(spec=ScaleObj, value=fake_block) + query = mocker.Mock(spec=ScaleType, value=fake_block) from_query = ColdkeySwapDisputeInfo.from_query(coldkey_ss58, query) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 072be4b650..84127a7da9 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2,9 +2,10 @@ import unittest.mock as mock import pytest -from async_substrate_interface.types import Runtime, ScaleObj +from async_substrate_interface.types import Runtime from bittensor_wallet import Wallet from scalecodec import GenericCall +from scalecodec.base import ScaleType from bittensor import u64_normalized_float from bittensor.core import async_subtensor, settings @@ -2829,7 +2830,9 @@ async def test_get_all_neuron_certificates(mocker, subtensor): @pytest.mark.asyncio async def test_get_timestamp(mocker, subtensor): fake_block = 1000 - mocked_query = mocker.AsyncMock(return_value=ScaleObj(1740586018 * 1000)) + mock_return = mocker.MagicMock(spec=ScaleType) + mock_return.value = 1740586018 * 1000 + mocked_query = mocker.AsyncMock(return_value=mock_return) mocker.patch.object(subtensor.substrate, "query", mocked_query) expected_result = datetime.datetime( 2025, 2, 26, 16, 6, 58, tzinfo=datetime.timezone.utc diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 0a55814fd7..da34c5970e 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -6,9 +6,10 @@ import pytest import websockets from async_substrate_interface import sync_substrate -from async_substrate_interface.types import Runtime, ScaleObj +from async_substrate_interface.types import Runtime from bittensor_wallet import Wallet from scalecodec import GenericCall +from scalecodec.base import ScaleType from bittensor import StakeInfo from bittensor.core import settings @@ -3096,7 +3097,9 @@ def test_get_all_neuron_certificates(mocker, subtensor): def test_get_timestamp(mocker, subtensor): fake_block = 1000 - mocked_query = mocker.MagicMock(return_value=ScaleObj(1740586018 * 1000)) + mock_return = mocker.MagicMock(spec=ScaleType) + mock_return.value = 1740586018 * 1000 + mocked_query = mocker.MagicMock(return_value=mock_return) mocker.patch.object(subtensor.substrate, "query", mocked_query) expected_result = datetime.datetime( 2025, 2, 26, 16, 6, 58, tzinfo=datetime.timezone.utc From d72d6164434af0ef81fde6a2e6379da9162a372f Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 14:18:32 +0200 Subject: [PATCH 03/38] Remove reuse block hash upstream logic --- bittensor/core/async_subtensor.py | 44 +------------ bittensor/core/chain_data/coldkey_swap.py | 2 +- bittensor/core/subtensor.py | 2 +- .../chain_data/test_coldkey_swap.py | 2 +- .../extrinsics/asyncex/test_registration.py | 2 +- .../extrinsics/asyncex/test_root.py | 2 - tests/unit_tests/test_async_subtensor.py | 62 +++---------------- tests/unit_tests/test_subtensor.py | 5 -- 8 files changed, 13 insertions(+), 108 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d142a71473..541b197872 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -485,6 +485,8 @@ async def determine_block_hash( return block_hash if block is not None: return await self.get_block_hash(block) + if reuse_block: + return self.substrate.last_block_hash return None async def _runtime_method_exists( @@ -640,7 +642,6 @@ async def get_hyperparameter( storage_function=param_name, params=[netuid], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return getattr(result, "value", result) @@ -778,7 +779,6 @@ async def query_constant( module_name=module_name, constant_name=constant_name, block_hash=block_hash, - reuse_block_hash=reuse_block, ) async def query_map( @@ -813,7 +813,6 @@ async def query_map( storage_function=name, params=params, block_hash=block_hash, - reuse_block_hash=reuse_block, ) return result @@ -847,7 +846,6 @@ async def query_map_subtensor( storage_function=name, params=params, block_hash=block_hash, - reuse_block_hash=reuse_block, ) async def query_module( @@ -883,7 +881,6 @@ async def query_module( storage_function=name, params=params, block_hash=block_hash, - reuse_block_hash=reuse_block, ) async def query_runtime_api( @@ -950,7 +947,6 @@ async def query_subtensor( storage_function=name, params=params, block_hash=block_hash, - reuse_block_hash=reuse_block, ) async def state_call( @@ -985,7 +981,6 @@ async def state_call( method="state_call", params=[method, data], block_hash=block_hash, - reuse_block_hash=reuse_block, ) # Common subtensor methods ========================================================================================= @@ -1178,7 +1173,6 @@ async def bonds( storage_function="Bonds", params=[storage_index], block_hash=block_hash, - reuse_block_hash=reuse_block, ) b_map = [] async for uid, b in b_map_encoded: @@ -1294,7 +1288,6 @@ async def does_hotkey_exist( storage_function="Owner", params=[hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return_val = ( False @@ -1620,7 +1613,6 @@ async def get_all_subnets_netuid( module="SubtensorModule", storage_function="NetworksAdded", block_hash=block_hash, - reuse_block_hash=reuse_block, ) subnets = [] if result.records: @@ -1697,7 +1689,6 @@ async def get_balance( storage_function="Account", params=[address], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return Balance(balance["data"]["free"]) @@ -1871,7 +1862,6 @@ async def get_children( storage_function="ChildKeys", params=[hotkey_ss58, netuid], block_hash=block_hash, - reuse_block_hash=reuse_block, ) if children: formatted_children = [] @@ -1927,7 +1917,6 @@ async def get_children_pending( block_hash, reuse_block, ), - reuse_block_hash=reuse_block, ) pending_value = getattr(response, "value", response) children, cooldown = cast( @@ -1980,7 +1969,6 @@ async def get_coldkey_swap_announcement( storage_function="ColdkeySwapAnnouncements", params=[coldkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) if query is None: return None @@ -2018,7 +2006,6 @@ async def get_coldkey_swap_announcements( module="SubtensorModule", storage_function="ColdkeySwapAnnouncements", block_hash=block_hash, - reuse_block_hash=reuse_block, ) return [ ColdkeySwapAnnouncementInfo.from_record(record) @@ -2054,7 +2041,6 @@ async def get_coldkey_swap_announcement_delay( module="SubtensorModule", storage_function="ColdkeySwapAnnouncementDelay", block_hash=block_hash, - reuse_block_hash=reuse_block, ) value = getattr(query, "value", query) return cast(int, value) if value is not None else 0 @@ -2088,7 +2074,6 @@ async def get_coldkey_swap_reannouncement_delay( module="SubtensorModule", storage_function="ColdkeySwapReannouncementDelay", block_hash=block_hash, - reuse_block_hash=reuse_block, ) value = getattr(query, "value", query) return cast(int, value) if value is not None else 0 @@ -2125,7 +2110,6 @@ async def get_coldkey_swap_dispute( storage_function="ColdkeySwapDisputes", params=[coldkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) if query is None: return None @@ -2161,7 +2145,6 @@ async def get_coldkey_swap_disputes( module="SubtensorModule", storage_function="ColdkeySwapDisputes", block_hash=block_hash, - reuse_block_hash=reuse_block, ) return [ ColdkeySwapDisputeInfo.from_record(record) async for record in query_map @@ -2303,7 +2286,6 @@ async def get_commitment_metadata( storage_function="CommitmentOf", params=[netuid, hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) if commit_data is None: return "" @@ -2597,7 +2579,6 @@ async def get_delegate_identities( module="SubtensorModule", storage_function="IdentitiesV2", block_hash=block_hash, - reuse_block_hash=reuse_block, ) return { @@ -2749,7 +2730,6 @@ async def get_existential_deposit( module_name="Balances", constant_name="ExistentialDeposit", block_hash=block_hash, - reuse_block_hash=reuse_block, ) if result is None: @@ -2833,7 +2813,6 @@ async def get_hotkey_owner( storage_function="Owner", params=[hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) exists = False if hk_owner_query: @@ -2871,7 +2850,6 @@ async def get_last_bonds_reset( storage_function="LastBondsReset", params=[netuid, hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return block @@ -3407,7 +3385,6 @@ async def get_netuids_for_hotkey( storage_function="IsNetworkMember", params=[hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) netuids = [] if result.records: @@ -3493,7 +3470,6 @@ async def get_neuron_for_pubkey_and_subnet( storage_function="Uids", params=[netuid, hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) if (uid := getattr(uid_query, "value", None)) is None: return NeuronInfo.get_null_neuron() @@ -3571,7 +3547,6 @@ async def get_owned_hotkeys( storage_function="OwnedHotkeys", params=[coldkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] @@ -3608,7 +3583,6 @@ async def get_parents( storage_function="ParentKeys", params=[hotkey_ss58, netuid], block_hash=block_hash, - reuse_block_hash=reuse_block, ) if parents: formatted_parents = [] @@ -3652,7 +3626,6 @@ async def get_proxies( module="Proxy", storage_function="Proxies", block_hash=block_hash, - reuse_block_hash=reuse_block, ) proxies = {} @@ -3697,7 +3670,6 @@ async def get_proxies_for_real_account( storage_function="Proxies", params=[real_account_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return ProxyInfo.from_query(query) @@ -3735,7 +3707,6 @@ async def get_proxy_announcement( storage_function="Announcements", params=[delegate_account_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) query_value = getattr(query, "value", query) return ProxyAnnouncementInfo.from_dict(cast(list[Any], query_value)[0]) @@ -3771,7 +3742,6 @@ async def get_proxy_announcements( module="Proxy", storage_function="Announcements", block_hash=block_hash, - reuse_block_hash=reuse_block, ) announcements = {} async for record in query_map: @@ -3952,7 +3922,6 @@ async def get_root_claim_type( storage_function="RootClaimType", params=[coldkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) query_value = getattr(query, "value", query) claim_type = cast(dict[str, Any], query_value) @@ -4002,7 +3971,6 @@ async def get_root_alpha_dividends_per_subnet( storage_function="RootAlphaDividendsPerSubnet", params=[netuid, hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) value = getattr(query, "value", query) return Balance.from_rao(cast(int, value)).set_unit(netuid=netuid) @@ -4071,7 +4039,6 @@ async def get_root_claimable_all_rates( storage_function="RootClaimable", params=[hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) query_value = getattr(query, "value", query) bits_list = next(iter(cast(list[list[tuple[int, FixedPoint]]], query_value))) @@ -4171,7 +4138,6 @@ async def get_root_claimed( storage_function="RootClaimed", params=[netuid, hotkey_ss58, coldkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) value = getattr(query, "value", query) return Balance.from_rao(cast(int, value)).set_unit(netuid=netuid) @@ -4894,7 +4860,6 @@ async def get_total_subnets( storage_function="TotalNetworks", params=[], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return getattr(result, "value", None) @@ -5013,7 +4978,6 @@ async def get_vote_data( storage_function="Voting", params=[proposal_hash], block_hash=block_hash, - reuse_block_hash=reuse_block, ), ) @@ -5052,7 +5016,6 @@ async def get_uid_for_hotkey_on_subnet( storage_function="Uids", params=[netuid, hotkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return cast(Optional[int], getattr(result, "value", result)) @@ -5657,7 +5620,6 @@ async def query_identity( storage_function="IdentitiesV2", params=[coldkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) if not identity_info: @@ -5778,7 +5740,6 @@ async def subnet_exists( storage_function="NetworksAdded", params=[netuid], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return getattr(result, "value", False) @@ -5945,7 +5906,6 @@ async def weights( storage_function="Weights", params=[storage_index], block_hash=block_hash, - reuse_block_hash=reuse_block, ) w_map = [] async for uid, w in w_map_encoded: diff --git a/bittensor/core/chain_data/coldkey_swap.py b/bittensor/core/chain_data/coldkey_swap.py index c323a84a55..5a7d5f9e45 100644 --- a/bittensor/core/chain_data/coldkey_swap.py +++ b/bittensor/core/chain_data/coldkey_swap.py @@ -32,7 +32,7 @@ class ColdkeySwapAnnouncementInfo: @classmethod def from_query( - cls, coldkey_ss58: str, query: ScaleType + cls, coldkey_ss58: str, query: Optional[ScaleType] ) -> Optional["ColdkeySwapAnnouncementInfo"]: """ Creates a ColdkeySwapAnnouncementInfo object from a Substrate query result. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 73c2299835..68265eccaf 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3244,7 +3244,7 @@ def get_root_claim_type( Parameters: coldkey_ss58: The SS58 address of the coldkey whose root claim preference to query. - block: The block number to query. Do not specify if using `block_hash` or `reuse_block`. + block: The block number to query. Returns: diff --git a/tests/unit_tests/chain_data/test_coldkey_swap.py b/tests/unit_tests/chain_data/test_coldkey_swap.py index 157f23b799..db870030b5 100644 --- a/tests/unit_tests/chain_data/test_coldkey_swap.py +++ b/tests/unit_tests/chain_data/test_coldkey_swap.py @@ -10,7 +10,7 @@ def test_coldkey_swap_announcement_info_from_query_none(mocker): """Test from_query returns None when query has no value.""" # Prep coldkey_ss58 = mocker.Mock(spec=str) - query = mocker.Mock(spec=ScaleType) + query = None # Call from_query = ColdkeySwapAnnouncementInfo.from_query(coldkey_ss58, query) diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index 278d20c2c8..5e853e24af 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -232,7 +232,7 @@ async def test_register_extrinsic_already_registered(subtensor, fake_wallet, moc block_hash=subtensor.substrate.get_chain_head.return_value, ) assert success is True - assert message == f"Already registered." + assert message == "Already registered." @pytest.mark.asyncio diff --git a/tests/unit_tests/extrinsics/asyncex/test_root.py b/tests/unit_tests/extrinsics/asyncex/test_root.py index 87b95f0776..42b73ce266 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_root.py +++ b/tests/unit_tests/extrinsics/asyncex/test_root.py @@ -95,7 +95,6 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): storage_function="Uids", params=[0, "fake_hotkey_address"], block_hash=None, - reuse_block_hash=False, ) assert result.success is True assert result.message == "Success" @@ -329,7 +328,6 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc storage_function="Uids", params=[0, "fake_hotkey_address"], block_hash=None, - reuse_block_hash=False, ) assert result.success is False diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 84127a7da9..f4ae3b17af 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -336,7 +336,6 @@ async def test_get_total_subnets(subtensor, mocker): storage_function="TotalNetworks", params=[], block_hash=fake_block_hash, - reuse_block_hash=False, ) @@ -369,7 +368,6 @@ async def test_get_subnets(subtensor, mocker, records, response): module="SubtensorModule", storage_function="NetworksAdded", block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result == response @@ -398,7 +396,9 @@ async def test_is_hotkey_delegate(subtensor, mocker, hotkey_ss58_in_result): # Asserts assert result == hotkey_ss58_in_result - mocked_get_delegates.assert_called_once_with(block_hash=None, reuse_block=True) + mocked_get_delegates.assert_called_once_with( + block_hash=subtensor.substrate.last_block_hash, reuse_block=True + ) @pytest.mark.parametrize( @@ -611,7 +611,6 @@ async def test_get_balance(subtensor, mocker): storage_function="Account", params=[fake_address], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=reuse_block, ) mocked_balance.assert_called_once_with( subtensor.substrate.query.return_value.__getitem__.return_value.__getitem__.return_value @@ -691,11 +690,11 @@ async def test_get_netuids_for_hotkey_with_records(subtensor, mocker): subtensor.substrate.query_map = mocked_substrate_query_map fake_hotkey_ss58 = "hotkey_58" - fake_block_hash = None + fake_block_hash = subtensor.substrate.last_block_hash # Call result = await subtensor.get_netuids_for_hotkey( - hotkey_ss58=fake_hotkey_ss58, block_hash=fake_block_hash, reuse_block=True + hotkey_ss58=fake_hotkey_ss58, reuse_block=True ) # Assertions @@ -704,7 +703,6 @@ async def test_get_netuids_for_hotkey_with_records(subtensor, mocker): storage_function="IsNetworkMember", params=[fake_hotkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=True, ) assert result == expected_response @@ -726,11 +724,11 @@ async def test_get_netuids_for_hotkey_without_records(subtensor, mocker): subtensor.substrate.query_map = mocked_substrate_query_map fake_hotkey_ss58 = "hotkey_58" - fake_block_hash = None + fake_block_hash = subtensor.substrate.last_block_hash # Call result = await subtensor.get_netuids_for_hotkey( - hotkey_ss58=fake_hotkey_ss58, block_hash=fake_block_hash, reuse_block=True + hotkey_ss58=fake_hotkey_ss58, block_hash=None, reuse_block=True ) # Assertions @@ -739,7 +737,6 @@ async def test_get_netuids_for_hotkey_without_records(subtensor, mocker): storage_function="IsNetworkMember", params=[fake_hotkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=True, ) assert result == expected_response @@ -770,7 +767,6 @@ async def test_subnet_exists(subtensor, mocker): storage_function="NetworksAdded", params=[fake_netuid], block_hash=fake_block_hash, - reuse_block_hash=fake_reuse_block_hash, ) assert result == mocked_substrate_query.return_value.value @@ -808,7 +804,6 @@ async def test_get_hyperparameter_happy_path(subtensor, mocker): storage_function=fake_param_name, params=[fake_netuid], block_hash=fake_block_hash, - reuse_block_hash=fake_reuse_block_hash, ) assert result == mocked_substrate_query.return_value.value @@ -913,7 +908,6 @@ async def test_get_existential_deposit_happy_path(subtensor, mocker): module_name="Balances", constant_name="ExistentialDeposit", block_hash=fake_block_hash, - reuse_block_hash=fake_reuse_block_hash, ) spy_balance_from_rao.assert_called_once_with( mocked_substrate_get_constant.return_value.value @@ -947,7 +941,6 @@ async def test_get_existential_deposit_raise_exception(subtensor, mocker): module_name="Balances", constant_name="ExistentialDeposit", block_hash=fake_block_hash, - reuse_block_hash=fake_reuse_block_hash, ) spy_balance_from_rao.assert_not_called() @@ -1065,7 +1058,6 @@ async def test_get_neuron_for_pubkey_and_subnet_success(subtensor, mocker): storage_function="Uids", params=[fake_netuid, fake_hotkey], block_hash=None, - reuse_block_hash=False, ) subtensor.substrate.runtime_call.assert_awaited_once() subtensor.substrate.runtime_call.assert_called_once_with( @@ -1105,7 +1097,6 @@ async def test_get_neuron_for_pubkey_and_subnet_uid_not_found(subtensor, mocker) storage_function="Uids", params=[fake_netuid, fake_hotkey], block_hash=None, - reuse_block_hash=False, ) mocked_get_null_neuron.assert_called_once() assert result == "null_neuron" @@ -1144,7 +1135,6 @@ async def test_get_neuron_for_pubkey_and_subnet_rpc_result_empty(subtensor, mock storage_function="Uids", params=[fake_netuid, fake_hotkey], block_hash=None, - reuse_block_hash=False, ) subtensor.substrate.runtime_call.assert_called_once_with( "NeuronInfoRuntimeApi", @@ -1389,7 +1379,6 @@ async def test_query_identity_successful(subtensor, mocker): storage_function="IdentitiesV2", params=[fake_coldkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result == ChainIdentity( additional="Additional", @@ -1420,7 +1409,6 @@ async def test_query_identity_no_info(subtensor, mocker): storage_function="IdentitiesV2", params=[fake_coldkey_ss58], block_hash=None, - reuse_block_hash=False, ) assert result is None @@ -1450,7 +1438,6 @@ async def test_query_identity_type_error(subtensor, mocker): storage_function="IdentitiesV2", params=[fake_coldkey_ss58], block_hash=None, - reuse_block_hash=False, ) assert result is None @@ -1481,7 +1468,6 @@ async def mock_query_map(**_): storage_function="Weights", params=[fake_netuid], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result == [(0, [(1, 10), (2, 20)]), (1, [(0, 15), (2, 25)])] @@ -1512,7 +1498,6 @@ async def mock_query_map(**_): storage_function="Bonds", params=[fake_netuid], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result == [(0, [(1, 100), (2, 200)]), (1, [(0, 150), (2, 250)])] @@ -1539,7 +1524,6 @@ async def test_does_hotkey_exist_true(subtensor, mocker): storage_function="Owner", params=[fake_hotkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result is True @@ -1564,7 +1548,6 @@ async def test_does_hotkey_exist_false_for_specific_account(subtensor, mocker): storage_function="Owner", params=[fake_hotkey_ss58], block_hash=None, - reuse_block_hash=False, ) assert result is False @@ -1593,7 +1576,6 @@ async def test_get_hotkey_owner_successful(subtensor, mocker): storage_function="Owner", params=[fake_hotkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=False, ) mocked_does_hotkey_exist.assert_awaited_once_with( fake_hotkey_ss58, block_hash=fake_block_hash @@ -1622,7 +1604,6 @@ async def test_get_hotkey_owner_non_existent_hotkey(subtensor, mocker): storage_function="Owner", params=[fake_hotkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result is None @@ -1899,7 +1880,6 @@ async def test_get_children_success(subtensor, mocker): module="SubtensorModule", storage_function="ChildKeys", params=[fake_hotkey, fake_netuid], - reuse_block_hash=False, ) mocked_decode_account_id.assert_has_calls( [mocker.call("child_key_1"), mocker.call("child_key_2")] @@ -1927,7 +1907,6 @@ async def test_get_children_no_children(subtensor, mocker): module="SubtensorModule", storage_function="ChildKeys", params=[fake_hotkey, fake_netuid], - reuse_block_hash=False, ) assert result == (True, [], "") @@ -1957,7 +1936,6 @@ async def test_get_children_substrate_request_exception(subtensor, mocker): module="SubtensorModule", storage_function="ChildKeys", params=[fake_hotkey, fake_netuid], - reuse_block_hash=False, ) mocked_format_error_message.assert_called_once_with(fake_exception) assert result == (False, [], "Formatted error message") @@ -1998,7 +1976,6 @@ async def test_get_parents_success(subtensor, mocker): module="SubtensorModule", storage_function="ParentKeys", params=[fake_hotkey, fake_netuid], - reuse_block_hash=False, ) mocked_decode_account_id.assert_has_calls( [mocker.call("parent_key_1"), mocker.call("parent_key_2")] @@ -2026,7 +2003,6 @@ async def test_get_parents_no_parents(subtensor, mocker): module="SubtensorModule", storage_function="ParentKeys", params=[fake_hotkey, fake_netuid], - reuse_block_hash=False, ) assert result == [] @@ -2077,7 +2053,6 @@ async def test_get_children_pending(mock_substrate, subtensor): storage_function="PendingChildKeys", params=[1, "hotkey_ss58"], block_hash=None, - reuse_block_hash=False, ) @@ -2198,7 +2173,6 @@ async def test_get_vote_data_success(subtensor, mocker): storage_function="Voting", params=[fake_proposal_hash], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result == mocked_proposal_vote_data @@ -2224,7 +2198,6 @@ async def test_get_vote_data_no_data(subtensor, mocker): storage_function="Voting", params=[fake_proposal_hash], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result is None @@ -2286,7 +2259,6 @@ async def test_get_delegate_identities(subtensor, mocker): module="SubtensorModule", storage_function="IdentitiesV2", block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result["delegate1_ss58"].name == "Chain Delegate 1" @@ -2314,7 +2286,6 @@ async def test_is_hotkey_registered_true(subtensor, mocker): storage_function="Uids", params=[fake_netuid, fake_hotkey_ss58], block_hash=None, - reuse_block_hash=False, ) assert result is True @@ -2341,7 +2312,6 @@ async def test_is_hotkey_registered_false(subtensor, mocker): storage_function="Uids", params=[fake_netuid, fake_hotkey_ss58], block_hash=None, - reuse_block_hash=False, ) assert result is False @@ -2369,7 +2339,6 @@ async def test_get_uid_for_hotkey_on_subnet_registered(subtensor, mocker): storage_function="Uids", params=[fake_netuid, fake_hotkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result == fake_uid @@ -2397,7 +2366,6 @@ async def test_get_uid_for_hotkey_on_subnet_not_registered(subtensor, mocker): storage_function="Uids", params=[fake_netuid, fake_hotkey_ss58], block_hash=fake_block_hash, - reuse_block_hash=False, ) assert result is None @@ -2823,7 +2791,6 @@ async def test_get_all_neuron_certificates(mocker, subtensor): storage_function="NeuronCertificates", params=[fake_netuid], block_hash=None, - reuse_block_hash=False, ) @@ -2869,7 +2836,6 @@ async def test_get_owned_hotkeys_happy_path(subtensor, mocker): storage_function="OwnedHotkeys", params=[fake_coldkey], block_hash=None, - reuse_block_hash=False, ) assert result == [mocked_decode_account_id.return_value] mocked_decode_account_id.assert_called_once_with(fake_hotkey) @@ -2892,7 +2858,6 @@ async def test_get_owned_hotkeys_return_empty(subtensor, mocker): storage_function="OwnedHotkeys", params=[fake_coldkey], block_hash=None, - reuse_block_hash=False, ) assert result == [] @@ -4893,7 +4858,6 @@ async def test_get_root_claim_type(mocker, subtensor, fake_result, expected_resu storage_function="RootClaimType", params=[fake_coldkey_ss58], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) assert result == expected_result @@ -4949,7 +4913,6 @@ async def test_get_root_claimable_all_rates(mocker, subtensor): storage_function="RootClaimable", params=[hotkey_ss58], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_fixed_to_float.assert_called_once_with({"bits": 6520190}, frac_bits=32) assert result == {14: mocked_fixed_to_float.return_value} @@ -5033,7 +4996,6 @@ async def test_get_root_claimed(mocker, subtensor): storage_function="RootClaimed", params=[netuid, hotkey_ss58, coldkey_ss58], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) assert result == Balance.from_rao(1).set_unit(netuid) @@ -5211,7 +5173,6 @@ async def test_get_proxies(subtensor, mocker): module="Proxy", storage_function="Proxies", block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_query_map_record.assert_called_once_with(fake_record) assert result == {fake_real_account: [fake_proxy_list]} @@ -5245,7 +5206,6 @@ async def test_get_proxies_for_real_account(subtensor, mocker): storage_function="Proxies", params=[fake_real_account_ss58], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_query.assert_called_once_with(mocked_query.return_value) assert result == mocked_from_query.return_value @@ -5278,7 +5238,6 @@ async def test_get_proxy_announcement(subtensor, mocker): storage_function="Announcements", params=[fake_delegate_account_ss58], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_dict.assert_called_once_with(mocked_query.return_value.value[0]) assert result == mocked_from_dict.return_value @@ -5319,7 +5278,6 @@ async def test_get_proxy_announcements(subtensor, mocker): module="Proxy", storage_function="Announcements", block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_query_map_record.assert_called_once_with(fake_record) assert result == {fake_delegate: fake_proxies_list} @@ -6228,7 +6186,6 @@ async def test_get_coldkey_swap_announcement(subtensor, mocker): storage_function="ColdkeySwapAnnouncements", params=[fake_coldkey_ss58], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_query.assert_called_once_with( coldkey_ss58=fake_coldkey_ss58, query=mocked_query.return_value @@ -6260,7 +6217,6 @@ async def test_get_coldkey_swap_announcements(subtensor, mocker): module="SubtensorModule", storage_function="ColdkeySwapAnnouncements", block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_record.assert_called_once_with(fake_record) assert result == [mocked_from_record.return_value] @@ -6282,7 +6238,6 @@ async def test_get_coldkey_swap_announcement_delay(subtensor, mocker): module="SubtensorModule", storage_function="ColdkeySwapAnnouncementDelay", block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) assert result == mocked_query.return_value.value @@ -6303,7 +6258,6 @@ async def test_get_coldkey_swap_reannouncement_delay(subtensor, mocker): module="SubtensorModule", storage_function="ColdkeySwapReannouncementDelay", block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) assert result == mocked_query.return_value.value @@ -6428,7 +6382,6 @@ async def test_get_coldkey_swap_dispute(subtensor, mocker): storage_function="ColdkeySwapDisputes", params=[fake_coldkey_ss58], block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_query.assert_called_once_with( coldkey_ss58=fake_coldkey_ss58, query=mocked_query.return_value @@ -6460,7 +6413,6 @@ async def test_get_coldkey_swap_disputes(subtensor, mocker): module="SubtensorModule", storage_function="ColdkeySwapDisputes", block_hash=mocked_determine_block_hash.return_value, - reuse_block_hash=False, ) mocked_from_record.assert_called_once_with(fake_record) assert result == [mocked_from_record.return_value] diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index da34c5970e..3e63a7be24 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1664,7 +1664,6 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): fake_hotkey = "hotkey" mocked_get_last_bonds_reset = mocker.patch.object(subtensor, "get_last_bonds_reset") - mocked_decode_block = mocker.patch.object(subtensor_module, "decode_block") mocked_metagraph = mocker.MagicMock() subtensor.metagraph = mocked_metagraph @@ -1678,10 +1677,6 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): # Assertions mocked_metagraph.assert_called_once_with(fake_netuid, block=None) mocked_get_last_bonds_reset.assert_called_once_with(fake_netuid, fake_hotkey, None) - mocked_decode_block.assert_called_once_with( - mocked_get_last_bonds_reset.return_value - ) - assert result == mocked_decode_block.return_value def test_min_allowed_weights(subtensor, mocker): From f9f84ed008ee84c0c4571404600b5748990a4f79 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 15:50:00 +0200 Subject: [PATCH 04/38] Checkin --- bittensor/core/async_subtensor.py | 64 ++++++------- bittensor/core/chain_data/coldkey_swap.py | 9 +- bittensor/core/chain_data/proxy.py | 31 ++++--- bittensor/core/chain_data/utils.py | 20 +++-- .../core/chain_data/weight_commit_info.py | 16 ++-- bittensor/core/subtensor.py | 22 ++--- bittensor/utils/__init__.py | 44 ++++++--- tests/unit_tests/chain_data/test_utils.py | 89 ++----------------- 8 files changed, 114 insertions(+), 181 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 541b197872..7c3525011b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -38,7 +38,6 @@ SubnetIdentity, SubnetInfo, WeightCommitInfo, - decode_account_id, ) from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.delegate_info import DelegatedInfo @@ -891,7 +890,7 @@ async def query_runtime_api( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[Any]: + ) -> Any: """Queries the runtime API of the Bittensor blockchain, providing a way to interact with the underlying runtime and retrieve data encoded in Scale Bytes format. Use this function for nonstandard queries to the runtime environment, if these cannot be accessed through other, standard getter methods. @@ -915,7 +914,7 @@ async def query_runtime_api( result = await self.substrate.runtime_call( runtime_api, method, params, block_hash ) - return result.value + return result async def query_subtensor( self, @@ -1174,12 +1173,12 @@ async def bonds( params=[storage_index], block_hash=block_hash, ) - b_map = [] - async for uid, b in b_map_encoded: - if b.value is not None: - b_map.append((uid, b.value)) + bond_map = [] + async for uid, bond in b_map_encoded: + if bond is not None: + bond_map.append((uid, bond)) - return b_map + return bond_map async def commit_reveal_enabled( self, @@ -1411,7 +1410,7 @@ async def get_all_commitments( result = {} async for id_, value in query: try: - result[decode_account_id(id_[0])] = decode_metadata(value) + result[id_] = decode_metadata(value) except Exception as error: logging.error( f"Error decoding [red]{id_}[/red] and [red]{value}[/red]: {error}" @@ -1532,7 +1531,7 @@ async def get_all_neuron_certificates( ) output = {} async for key, item in query_certificates: - output[decode_account_id(key)] = Certificate(item.value) + output[key] = Certificate(item) return output async def get_all_revealed_commitments( @@ -1654,8 +1653,7 @@ async def get_auto_stakes( ) pairs = {} - async for netuid, destination in query: - hotkey_ss58 = decode_account_id(destination.value[0]) + async for netuid, hotkey_ss58 in query: if hotkey_ss58: pairs[int(netuid)] = hotkey_ss58 @@ -1865,9 +1863,8 @@ async def get_children( ) if children: formatted_children = [] - for proportion, child in children.value: + for proportion, formatted_child in children.value: # Convert U64 to int - formatted_child = decode_account_id(child[0]) normalized_proportion = u64_normalized_float(proportion) formatted_children.append((normalized_proportion, formatted_child)) return True, formatted_children, "" @@ -1928,7 +1925,7 @@ async def get_children_pending( [ ( u64_normalized_float(proportion), - decode_account_id(child[0]), + child, ) for proportion, child in children ], @@ -2384,10 +2381,8 @@ async def get_crowdloan_contributions( if query.records: async for record in query: - if record[1].value: - result[decode_account_id(record[0])] = Balance.from_rao( - record[1].value - ) + if record[1]: + result[record[0]] = Balance.from_rao(record[1]) return result @@ -2498,7 +2493,7 @@ async def get_crowdloans( if query.records: async for c_id, value_obj in query: - data = value_obj.value + data = value_obj if not data: continue crowdloans.append( @@ -2582,8 +2577,8 @@ async def get_delegate_identities( ) return { - decode_account_id(ss58_address[0]): ChainIdentity.from_dict( - decode_hex_identity_dict(identity.value), + ss58_address: ChainIdentity.from_dict( + decode_hex_identity_dict(identity), ) async for ss58_address, identity in identities } @@ -2980,9 +2975,7 @@ async def get_liquidity_list( # Fetch positions positions_values: list[tuple[dict, int, int]] = [] positions_storage_keys: list[StorageKey] = [] - async for _, p in positions_response: - position = p.value - + async for _, position in positions_response: tick_low_idx = position.get("tick_low")[0] tick_high_idx = position.get("tick_high")[0] positions_values.append((position, tick_low_idx, tick_high_idx)) @@ -3388,9 +3381,9 @@ async def get_netuids_for_hotkey( ) netuids = [] if result.records: - async for record in result: - if record[1].value: - netuids.append(record[0]) + async for netuid, is_member in result: + if is_member: + netuids.append(netuid) return netuids async def get_neuron_certificate( @@ -3549,7 +3542,7 @@ async def get_owned_hotkeys( block_hash=block_hash, ) - return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] + return owned_hotkeys or [] async def get_parents( self, @@ -3586,9 +3579,8 @@ async def get_parents( ) if parents: formatted_parents = [] - for proportion, parent in parents.value: + for proportion, formatted_child in parents.value: # Convert U64 to int - formatted_child = decode_account_id(parent[0]) normalized_proportion = u64_normalized_float(proportion) formatted_parents.append((normalized_proportion, formatted_child)) return formatted_parents @@ -4360,10 +4352,7 @@ async def get_stake_info_for_coldkeys( if query is None: return {} - return { - decode_account_id(ck): StakeInfo.list_from_dicts(st_info) - for ck, st_info in query - } + return {ck: StakeInfo.list_from_dicts(st_info) for ck, st_info in query} async def get_stake_for_hotkey( self, @@ -4456,7 +4445,7 @@ async def get_staking_hotkeys( params=[coldkey_ss58], block_hash=block_hash, ) - return [decode_account_id(hotkey[0]) for hotkey in result or []] + return result or [] async def get_start_call_delay( self, @@ -4691,6 +4680,7 @@ async def get_subnet_prices( prices = {} async for id_, current_sqrt_price in current_sqrt_prices: + # TODO investigate if we need to use fixed_to_decimal here instead current_sqrt_price = fixed_to_float(current_sqrt_price) current_price = current_sqrt_price * current_sqrt_price current_price_in_tao = Balance.from_rao(int(current_price * 1e9)) @@ -5909,7 +5899,7 @@ async def weights( ) w_map = [] async for uid, w in w_map_encoded: - w_map.append((uid, w.value)) + w_map.append((uid, w)) return w_map diff --git a/bittensor/core/chain_data/coldkey_swap.py b/bittensor/core/chain_data/coldkey_swap.py index 5a7d5f9e45..fb64df43c5 100644 --- a/bittensor/core/chain_data/coldkey_swap.py +++ b/bittensor/core/chain_data/coldkey_swap.py @@ -113,7 +113,7 @@ def from_query( return cls(coldkey=coldkey_ss58, disputed_block=int(query.value)) @classmethod - def from_record(cls, record: tuple) -> "ColdkeySwapDisputeInfo": + def from_record(cls, record: tuple[str, int]) -> "ColdkeySwapDisputeInfo": """ Creates a ColdkeySwapDisputeInfo object from a query_map record. @@ -124,12 +124,7 @@ def from_record(cls, record: tuple) -> "ColdkeySwapDisputeInfo": Returns: ColdkeySwapDisputeInfo object with dispute details for the coldkey from the record. """ - coldkey_ss58 = decode_account_id(record[0]) - val = record[1] - disputed_block = ( - int(val.value) if getattr(val, "value", None) is not None else int(val) - ) - return cls(coldkey=coldkey_ss58, disputed_block=disputed_block) + return cls(coldkey=record[0], disputed_block=int(record[1])) @dataclass diff --git a/bittensor/core/chain_data/proxy.py b/bittensor/core/chain_data/proxy.py index 8e663d26b3..465e6b9dfd 100644 --- a/bittensor/core/chain_data/proxy.py +++ b/bittensor/core/chain_data/proxy.py @@ -1,8 +1,7 @@ from dataclasses import dataclass from enum import Enum -from typing import Any, Optional, Union +from typing import Any, Optional, Union, Sequence -from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils.balance import Balance @@ -193,7 +192,7 @@ class ProxyInfo: delay: int @classmethod - def from_tuple(cls, data: tuple) -> list["ProxyInfo"]: + def from_tuple(cls, data: Sequence[dict[str, str | int]]) -> list["ProxyInfo"]: """Creates a list of ProxyInfo objects from chain proxy data. This method decodes the raw proxy data returned from the Proxy.Proxies storage function and creates @@ -210,8 +209,8 @@ def from_tuple(cls, data: tuple) -> list["ProxyInfo"]: """ return [ cls( - delegate=decode_account_id(proxy["delegate"]), - proxy_type=next(iter(proxy["proxy_type"].keys())), + delegate=proxy["delegate"], + proxy_type=proxy["proxy_type"], delay=proxy["delay"], ) for proxy in data @@ -245,7 +244,9 @@ def from_query(cls, query: Any) -> tuple[list["ProxyInfo"], Balance]: return cls.from_tuple(proxies), Balance.from_rao(balance) @classmethod - def from_query_map_record(cls, record: list) -> tuple[str, list["ProxyInfo"]]: + def from_query_map_record( + cls, record: tuple[str, tuple[list[dict[str, str | int]], int]] + ) -> tuple[str, list["ProxyInfo"]]: """Creates a dictionary mapping delegate addresses to their ProxyInfo lists from a query_map record. Processes a single record from a query_map call to the Proxy.Proxies storage function. Each record represents @@ -262,9 +263,9 @@ def from_query_map_record(cls, record: list) -> tuple[str, list["ProxyInfo"]]: """ # record[0] is the real account (key from storage) # record[1] is the value containing proxies data - real_account_ss58 = decode_account_id(record[0]) + real_account_ss58 = record[0] # list with proxies data is always in that path - proxy_data = cls.from_tuple(record[1].value[0][0]) + proxy_data = cls.from_tuple(record[1][0]) return real_account_ss58, proxy_data @@ -295,7 +296,9 @@ class ProxyAnnouncementInfo: height: int @classmethod - def from_dict(cls, data: tuple) -> list["ProxyAnnouncementInfo"]: + def from_dict( + cls, data: tuple[list[dict[str, str | int]], int] + ) -> list["ProxyAnnouncementInfo"]: """Creates a list of ProxyAnnouncementInfo objects from chain announcement data. This method decodes the raw announcement data returned from the Proxy.Announcements storage function. @@ -311,8 +314,8 @@ def from_dict(cls, data: tuple) -> list["ProxyAnnouncementInfo"]: """ return [ cls( - real=decode_account_id(next(iter(annt["real"]))), - call_hash="0x" + bytes(next(iter(annt["call_hash"]))).hex(), + real=annt["real"], + call_hash=annt["call_hash"], height=annt["height"], ) for annt in data[0] @@ -320,7 +323,7 @@ def from_dict(cls, data: tuple) -> list["ProxyAnnouncementInfo"]: @classmethod def from_query_map_record( - cls, record: tuple + cls, record: tuple[str, tuple[list[dict[str, str | int]], int]] ) -> tuple[str, list["ProxyAnnouncementInfo"]]: """Returns a list of ProxyAnnouncementInfo objects from a tuple of announcements data. @@ -335,9 +338,9 @@ def from_query_map_record( """ # record[0] is the real account (key from storage) # record[1] is the value containing announcements data - delegate = decode_account_id(record[0]) + delegate = record[0] # list with proxies data is always in that path - announcements_data = cls.from_dict(record[1].value[0]) + announcements_data = cls.from_dict(record[1]) return delegate, announcements_data diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index d6946c96ae..6c318b172b 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -5,7 +5,7 @@ from bittensor_wallet.utils import SS58_FORMAT from scalecodec import ScaleBytes -from scalecodec.base import RuntimeConfiguration, ScaleType +from scalecodec.base import RuntimeConfiguration from scalecodec.type_registry import load_type_registry_preset from scalecodec.utils.ss58 import ss58_encode @@ -136,10 +136,11 @@ def process_stake_data(stake_data: list) -> dict: def decode_metadata(metadata: dict) -> str: - commitment = metadata["info"]["fields"][0][0] - raw_bytes = next(iter(commitment.values())) - byte_tuple = raw_bytes[0] if raw_bytes else raw_bytes - return bytes(byte_tuple).decode("utf-8", errors="ignore") + commitment = metadata["info"]["fields"][0] + if isinstance(commitment, str): + return "" + hex_: str = next(iter(commitment.values())) + return bytes.fromhex(hex_.removeprefix("0x")).decode("utf-8", errors="ignore") def decode_block(data: bytes) -> int: @@ -177,7 +178,10 @@ def scale_decode_offset(data: bytes) -> int: else: return 4 - com_bytes, revealed_block = encoded_data + com_hex: str + revealed_block: int + com_hex, revealed_block = encoded_data + com_bytes = bytes.fromhex(com_hex.removeprefix("0x")) offset = scale_decode_offset(com_bytes) revealed_commitment = bytes(com_bytes[offset:]).decode("utf-8", errors="ignore") @@ -196,6 +200,6 @@ def decode_revealed_commitment_with_hotkey( """ key, data = encoded_data - ss58_address = decode_account_id(next(iter(key))) - block_data = tuple(decode_revealed_commitment(p) for p in data.value) + ss58_address = key + block_data = tuple(decode_revealed_commitment(p) for p in data) return ss58_address, block_data diff --git a/bittensor/core/chain_data/weight_commit_info.py b/bittensor/core/chain_data/weight_commit_info.py index 814ef54aca..3e6f13517a 100644 --- a/bittensor/core/chain_data/weight_commit_info.py +++ b/bittensor/core/chain_data/weight_commit_info.py @@ -46,6 +46,7 @@ def from_vec_u8(cls, data: tuple) -> tuple[str, str, int]: @classmethod def from_vec_u8_v2(cls, data: tuple) -> tuple[str, int, str, int]: """ + # TODO no it does not Creates a WeightCommitInfo instance Parameters: @@ -54,13 +55,10 @@ def from_vec_u8_v2(cls, data: tuple) -> tuple[str, int, str, int]: Returns: WeightCommitInfo: A new instance with the decoded data """ - account_id, commit_block, commit_data, round_number = data - - account_id_ = account_id[0] if isinstance(account_id, tuple) else account_id - commit_block = ( - commit_block[0] if isinstance(commit_block, tuple) else commit_block - ) - commit_data = commit_data[0] if isinstance(commit_data, tuple) else commit_data - commit_hex = "0x" + "".join(format(x, "02x") for x in commit_data) + account_id: str + commit_block: int + commit_hex: str + round_number: int + account_id, commit_block, commit_hex, round_number = data - return decode_account_id(account_id_), commit_block, commit_hex, round_number + return account_id, commit_block, commit_hex, round_number diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 68265eccaf..26efd83eaa 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -767,7 +767,7 @@ def query_runtime_api( block_hash = self.determine_block_hash(block) result = self.substrate.runtime_call(runtime_api, method, params, block_hash) - return result.value + return result def query_subtensor( self, @@ -833,12 +833,11 @@ def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo a subnet, or None if the query fails. """ block_hash = self.determine_block_hash(block=block) - query = self.substrate.runtime_call( + decoded = self.substrate.runtime_call( api="SubnetInfoRuntimeApi", method="get_all_dynamic_info", block_hash=block_hash, ) - decoded = query.decode() try: subnet_prices = self.get_subnet_prices(block=block) for sn in decoded: @@ -957,12 +956,12 @@ def bonds( params=[storage_index], block_hash=self.determine_block_hash(block), ) - b_map = [] - for uid, b in b_map_encoded: - if b.value is not None: - b_map.append((uid, b.value)) + bond_map = [] + for uid, bond in b_map_encoded: + if bond.value is not None: + bond_map.append((uid, bond)) - return b_map + return bond_map def commit_reveal_enabled(self, netuid: int, block: Optional[int] = None) -> bool: """Check if commit-reveal mechanism is enabled for a given subnet at a specific block. @@ -1123,7 +1122,7 @@ def get_all_commitments( result = {} for id_, value in query: try: - result[decode_account_id(id_[0])] = decode_metadata(value) + result[id_] = decode_metadata(value) except Exception as error: logging.error( f"Error decoding [red]{id_}[/red] and [red]{value}[/red]: {error}" @@ -1225,7 +1224,7 @@ def get_all_neuron_certificates( ) output = {} for key, item in query_certificates: - output[decode_account_id(key)] = Certificate(item.value) + output[key] = Certificate(item) return output def get_all_revealed_commitments( @@ -3948,6 +3947,7 @@ def get_timelocked_weight_commits( storage_function="TimelockedWeightCommits", params=[storage_index], block_hash=self.determine_block_hash(block=block), + page_size=1, ) commits = result.records[0][1] if result.records else [] @@ -4798,7 +4798,7 @@ def weights( params=[storage_index], block_hash=self.determine_block_hash(block), ) - w_map = [(uid, w.value or []) for uid, w in w_map_encoded] + w_map = [(uid, w or []) for uid, w in w_map_encoded] return w_map diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 52003f0017..bcbb3450a0 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -104,21 +104,37 @@ def __new__(cls, data: Union[str, 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(): - if isinstance(v, dict): - item = next(iter(v.values())) - else: - item = v - if isinstance(item, tuple): - try: - decoded_info[k] = bytes(item).decode() - except UnicodeDecodeError: - print(f"Could not decode: {k}: {item}") - else: - decoded_info[k] = item - return decoded_info + def get_decoded(data: Optional[str]) -> str: + """Decodes a hex-encoded string.""" + if data is None: + return "" + try: + return hex_to_bytes(data).decode() + except (UnicodeDecodeError, ValueError): + raise ValueError(f"Could not decode hex-encoded string: {data}") + + for key, value in info_dictionary.items(): + if isinstance(value, dict): + item = list(value.values())[0] + if isinstance(item, str) and item.startswith("0x"): + info_dictionary[key] = get_decoded(item) + else: + info_dictionary[key] = item + if key == "additional": + additional = [] + for item in value: + if isinstance(item, dict): + for k, v in item.items(): + additional.append((k, get_decoded(v))) + else: + if isinstance(item, (tuple, list)) and len(item) == 2: + k_, v = item + k = k_ if k_ is not None else "" + additional.append((k, get_decoded(v))) + info_dictionary[key] = additional + + return info_dictionary def ss58_to_vec_u8(ss58_address: str) -> list[int]: diff --git a/tests/unit_tests/chain_data/test_utils.py b/tests/unit_tests/chain_data/test_utils.py index 602e03c192..8339cc71eb 100644 --- a/tests/unit_tests/chain_data/test_utils.py +++ b/tests/unit_tests/chain_data/test_utils.py @@ -8,93 +8,20 @@ [ ( { + "block": 5097676, "deposit": 0, - "block": 5415815, "info": { - "fields": ( - ( - { - "Raw64": ( - ( - 51, - 98, - 99, - 54, - 49, - 48, - 57, - 102, - 49, - 101, - 49, - 51, - 102, - 102, - 56, - 102, - 55, - 101, - 98, - 54, - 97, - 102, - 54, - 49, - 53, - 101, - 49, - 102, - 56, - 101, - 49, - 55, - 99, - 57, - 97, - 100, - 100, - 48, - 97, - 50, - 56, - 98, - 99, - 48, - 50, - 54, - 55, - 57, - 52, - 99, - 56, - 54, - 97, - 101, - 50, - 56, - 57, - 57, - 50, - 99, - 102, - 48, - 52, - 53, - ), - ) - }, - ), - ) + "fields": [ + { + "Raw97": "0x7b27706565725f6964273a2027313244334b6f6f57524e7735344157347a725157655a4c32627568553850373167666f3950585151414855774541653468413334272c20276d6f64656c5f68756767696e67666163655f6964273a204e6f6e657d" + } + ] }, }, - "3bc6109f1e13ff8f7eb6af615e1f8e17c9add0a28bc026794c86ae28992cf045", + "{'peer_id': '12D3KooWRNw54AW4zrQWeZL2buhU8P71gfo9PXQQAHUwEAe4hA34', 'model_huggingface_id': None}", ), ( - { - "deposit": 0, - "block": 5866237, - "info": {"fields": (({"ResetBondsFlag": ()},),)}, - }, + {"block": 6161535, "deposit": 0, "info": {"fields": ["ResetBondsFlag"]}}, "", ), ], From d1cafc099a1f93d3339446cae436237baed743f1 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 16:43:51 +0200 Subject: [PATCH 05/38] Unit tests fixed --- bittensor/core/subtensor.py | 10 +- bittensor/utils/__init__.py | 34 +-- bittensor/utils/balance.py | 1 - .../chain_data/test_coldkey_swap.py | 6 +- tests/unit_tests/test_async_subtensor.py | 244 +++++++----------- tests/unit_tests/test_subtensor.py | 150 +---------- 6 files changed, 110 insertions(+), 335 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 26efd83eaa..e47759551f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1507,9 +1507,8 @@ def get_children( ) if children: formatted_children = [] - for proportion, child in children.value: + for proportion, formatted_child in children.value: # Convert U64 to int - formatted_child = decode_account_id(child[0]) normalized_proportion = u64_normalized_float(proportion) formatted_children.append((normalized_proportion, formatted_child)) return True, formatted_children, "" @@ -4582,6 +4581,7 @@ def query_identity( See the `Bittensor CLI documentation `_ for supported identity parameters. """ + print(coldkey_ss58) identity_info = self.substrate.query( module="SubtensorModule", storage_function="IdentitiesV2", @@ -4593,11 +4593,7 @@ def query_identity( return None try: - identity_data = ( - identity_info.value - if hasattr(identity_info, "value") - else identity_info - ) + identity_data = identity_info.value return ChainIdentity.from_dict( decode_hex_identity_dict(cast(dict[str, Any], identity_data)), ) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index bcbb3450a0..54a11316a3 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -102,38 +102,18 @@ 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]: +def decode_hex_identity_dict(info_dictionary: dict[str, dict | str]) -> dict[str, Any]: """Decodes a dictionary of hexadecimal identities.""" - def get_decoded(data: Optional[str]) -> str: - """Decodes a hex-encoded string.""" - if data is None: - return "" - try: - return hex_to_bytes(data).decode() - except (UnicodeDecodeError, ValueError): - raise ValueError(f"Could not decode hex-encoded string: {data}") - for key, value in info_dictionary.items(): if isinstance(value, dict): item = list(value.values())[0] - if isinstance(item, str) and item.startswith("0x"): - info_dictionary[key] = get_decoded(item) - else: - info_dictionary[key] = item - if key == "additional": - additional = [] - for item in value: - if isinstance(item, dict): - for k, v in item.items(): - additional.append((k, get_decoded(v))) - else: - if isinstance(item, (tuple, list)) and len(item) == 2: - k_, v = item - k = k_ if k_ is not None else "" - additional.append((k, get_decoded(v))) - info_dictionary[key] = additional - + else: + item = value + if isinstance(item, str) and item.startswith("0x"): + info_dictionary[key] = hex_to_bytes(item.removeprefix("0x")).decode() + else: + info_dictionary[key] = item return info_dictionary diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 0fc55b4d5e..6690f17fb3 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,6 +1,5 @@ from typing import Optional, TypedDict, Union -from scalecodec import ScaleType from scalecodec.utils.math import fixed_to_float as fixed_to_float from bittensor.core import settings diff --git a/tests/unit_tests/chain_data/test_coldkey_swap.py b/tests/unit_tests/chain_data/test_coldkey_swap.py index db870030b5..db93a8ee6e 100644 --- a/tests/unit_tests/chain_data/test_coldkey_swap.py +++ b/tests/unit_tests/chain_data/test_coldkey_swap.py @@ -71,11 +71,7 @@ def test_coldkey_swap_dispute_info_from_record(mocker): """Test from_record returns ColdkeySwapDisputeInfo from query_map record.""" decoded_coldkey = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" disputed_block = 999 - record = (mocker.Mock(), mocker.Mock(value=disputed_block)) - mocker.patch( - "bittensor.core.chain_data.coldkey_swap.decode_account_id", - return_value=decoded_coldkey, - ) + record = (decoded_coldkey, disputed_block) from_record = ColdkeySwapDisputeInfo.from_record(record) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index f4ae3b17af..21ba4bb12b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -577,7 +577,7 @@ async def test_query_runtime_api(subtensor, mocker): fake_block_hash, ) - assert result == mocked_runtime_call.return_value.value + assert result == mocked_runtime_call.return_value @pytest.mark.asyncio @@ -1040,7 +1040,7 @@ async def test_get_neuron_for_pubkey_and_subnet_success(subtensor, mocker): mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.Mock(value=fake_result), + return_value=fake_result, ) mocked_neuron_info = mocker.patch.object( async_subtensor.NeuronInfo, "from_dict", return_value="fake_neuron_info" @@ -1118,7 +1118,7 @@ async def test_get_neuron_for_pubkey_and_subnet_rpc_result_empty(subtensor, mock mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.Mock(value=None), + return_value=None, ) mocked_get_null_neuron = mocker.patch.object( async_subtensor.NeuronInfo, "get_null_neuron", return_value="null_neuron" @@ -1171,7 +1171,7 @@ async def test_neuron_for_uid_happy_path(subtensor, mocker): # Asserts mocked_null_neuron.assert_not_called() mocked_neuron_info_from_dict.assert_called_once_with( - subtensor.substrate.runtime_call.return_value.value + subtensor.substrate.runtime_call.return_value ) assert result == mocked_neuron_info_from_dict.return_value @@ -1213,11 +1213,7 @@ async def test_neuron_for_uid(subtensor, mocker): ) # no result in response - mocked_substrate_runtime_call = mocker.AsyncMock( - return_value=mocker.Mock( - value=None, - ), - ) + mocked_substrate_runtime_call = mocker.AsyncMock(return_value=None) subtensor.substrate.runtime_call = mocked_substrate_runtime_call mocked_neuron_info_from_dict = mocker.patch.object( @@ -1258,7 +1254,7 @@ async def test_get_delegated_no_block_hash_no_reuse(subtensor, mocker): None, ) mocked_delegated_list_from_dicts.assert_called_once_with( - subtensor.substrate.runtime_call.return_value.value + subtensor.substrate.runtime_call.return_value ) assert result == mocked_delegated_list_from_dicts.return_value @@ -1288,7 +1284,7 @@ async def test_get_delegated_with_block_hash(subtensor, mocker): fake_block_hash, ) mocked_delegated_list_from_dicts.assert_called_once_with( - subtensor.substrate.runtime_call.return_value.value + subtensor.substrate.runtime_call.return_value ) assert result == mocked_delegated_list_from_dicts.return_value @@ -1318,7 +1314,7 @@ async def test_get_delegated_with_reuse_block(subtensor, mocker): subtensor.substrate.last_block_hash, ) mocked_delegated_list_from_dicts.assert_called_once_with( - subtensor.substrate.runtime_call.return_value.value + subtensor.substrate.runtime_call.return_value ) assert result == mocked_delegated_list_from_dicts.return_value @@ -1329,11 +1325,7 @@ async def test_get_delegated_with_empty_result(subtensor, mocker): # Preps fake_coldkey_ss58 = "fake_ss58_address" - mocked_runtime_call = mocker.AsyncMock( - return_value=mocker.Mock( - value=None, - ), - ) + mocked_runtime_call = mocker.AsyncMock(return_value=None) subtensor.substrate.runtime_call = mocked_runtime_call # Call @@ -1355,15 +1347,17 @@ async def test_query_identity_successful(subtensor, mocker): # Preps fake_coldkey_ss58 = "test_key" fake_block_hash = "block_hash" - fake_identity_info = { - "additional": "Additional", - "description": "Description", - "discord": "", - "github_repo": "https://github.com/opentensor/bittensor", - "image": "", - "name": "Name", - "url": "https://www.example.com", - } + fake_identity_info = mocker.MagicMock( + value={ + "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 @@ -1449,8 +1443,8 @@ async def test_weights_successful(subtensor, mocker): fake_netuid = 1 fake_block_hash = "block_hash" fake_weights = [ - (0, mocker.AsyncMock(value=[(1, 10), (2, 20)])), - (1, mocker.AsyncMock(value=[(0, 15), (2, 25)])), + (0, [(1, 10), (2, 20)]), + (1, [(0, 15), (2, 25)]), ] async def mock_query_map(**_): @@ -1479,8 +1473,8 @@ async def test_bonds(subtensor, mocker): fake_netuid = 1 fake_block_hash = "block_hash" fake_bonds = [ - (0, mocker.Mock(value=[(1, 100), (2, 200)])), - (1, mocker.Mock(value=[(0, 150), (2, 250)])), + (0, [(1, 100), (2, 200)]), + (1, [(0, 150), (2, 250)]), ] async def mock_query_map(**_): @@ -1853,19 +1847,14 @@ async def test_get_children_success(subtensor, mocker): fake_netuid = 1 fake_children = mocker.Mock( value=[ - (1000, ["child_key_1"]), - (2000, ["child_key_2"]), + (1000, "decoded_child_key_1"), + (2000, "decoded_child_key_2"), ] ) mocked_query = mocker.AsyncMock(return_value=fake_children) subtensor.substrate.query = mocked_query - mocked_decode_account_id = mocker.Mock( - side_effect=["decoded_child_key_1", "decoded_child_key_2"] - ) - mocker.patch.object(async_subtensor, "decode_account_id", mocked_decode_account_id) - expected_formatted_children = [ (u64_normalized_float(1000), "decoded_child_key_1"), (u64_normalized_float(2000), "decoded_child_key_2"), @@ -1881,9 +1870,6 @@ async def test_get_children_success(subtensor, mocker): storage_function="ChildKeys", params=[fake_hotkey, fake_netuid], ) - mocked_decode_account_id.assert_has_calls( - [mocker.call("child_key_1"), mocker.call("child_key_2")] - ) assert result == (True, expected_formatted_children, "") @@ -1949,19 +1935,14 @@ async def test_get_parents_success(subtensor, mocker): fake_netuid = 1 fake_parents = mocker.Mock( value=[ - (1000, ["parent_key_1"]), - (2000, ["parent_key_2"]), + (1000, "decoded_parent_key_1"), + (2000, "decoded_parent_key_2"), ] ) mocked_query = mocker.AsyncMock(return_value=fake_parents) subtensor.substrate.query = mocked_query - mocked_decode_account_id = mocker.Mock( - side_effect=["decoded_parent_key_1", "decoded_parent_key_2"] - ) - mocker.patch.object(async_subtensor, "decode_account_id", mocked_decode_account_id) - expected_formatted_parents = [ (u64_normalized_float(1000), "decoded_parent_key_1"), (u64_normalized_float(2000), "decoded_parent_key_2"), @@ -1977,9 +1958,6 @@ async def test_get_parents_success(subtensor, mocker): storage_function="ParentKeys", params=[fake_hotkey, fake_netuid], ) - mocked_decode_account_id.assert_has_calls( - [mocker.call("parent_key_1"), mocker.call("parent_key_2")] - ) assert result == expected_formatted_parents @@ -2029,7 +2007,7 @@ async def test_get_children_pending(mock_substrate, subtensor): [ ( U64_MAX, - (tuple(bytearray(32)),), + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", ), ], 123, @@ -2209,32 +2187,28 @@ async def test_get_delegate_identities(subtensor, mocker): fake_block_hash = "block_hash" fake_chain_data = [ ( - ["delegate1_ss58"], - mocker.Mock( - value={ - "additional": "", - "description": "", - "discord": "", - "github_repo": "", - "image": "", - "name": "Chain Delegate 1", - "url": "", - }, - ), + "delegate1_ss58", + { + "additional": "", + "description": "", + "discord": "", + "github_repo": "", + "image": "", + "name": "Chain Delegate 1", + "url": "", + }, ), ( - ["delegate2_ss58"], - mocker.Mock( - value={ - "additional": "", - "description": "", - "discord": "", - "github_repo": "", - "image": "", - "name": "Chain Delegate 2", - "url": "", - }, - ), + "delegate2_ss58", + { + "additional": "", + "description": "", + "discord": "", + "github_repo": "", + "image": "", + "name": "Chain Delegate 2", + "url": "", + }, ), ] @@ -2243,9 +2217,6 @@ async def test_get_delegate_identities(subtensor, mocker): ) subtensor.substrate.query_map = mocked_query_map - mocked_decode_account_id = mocker.Mock(side_effect=lambda ss58: ss58) - mocker.patch.object(async_subtensor, "decode_account_id", mocked_decode_account_id) - mocked_decode_hex_identity_dict = mocker.Mock(side_effect=lambda data: data) mocker.patch.object( async_subtensor, "decode_hex_identity_dict", mocked_decode_hex_identity_dict @@ -2814,19 +2785,10 @@ async def test_get_owned_hotkeys_happy_path(subtensor, mocker): # Prep fake_coldkey = "fake_hotkey" fake_hotkey = "fake_hotkey" - fake_hotkeys = [ - [ - fake_hotkey, - ] - ] + fake_hotkeys = [fake_hotkey] mocked_subtensor = mocker.AsyncMock(return_value=fake_hotkeys) mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor) - mocked_decode_account_id = mocker.Mock() - mocker.patch.object( - async_subtensor, "decode_account_id", new=mocked_decode_account_id - ) - # Call result = await subtensor.get_owned_hotkeys(fake_coldkey) @@ -2837,8 +2799,7 @@ async def test_get_owned_hotkeys_happy_path(subtensor, mocker): params=[fake_coldkey], block_hash=None, ) - assert result == [mocked_decode_account_id.return_value] - mocked_decode_account_id.assert_called_once_with(fake_hotkey) + assert result == [fake_hotkey] @pytest.mark.asyncio @@ -3572,45 +3533,39 @@ async def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): fake_positions = [ [ (2,), - mocker.Mock( - value={ - "id": (2,), - "netuid": 2, - "tick_low": (206189,), - "tick_high": (208196,), - "liquidity": 1000000000000, - "fees_tao": {"bits": 0}, - "fees_alpha": {"bits": 0}, - } - ), + { + "id": (2,), + "netuid": 2, + "tick_low": (206189,), + "tick_high": (208196,), + "liquidity": 1000000000000, + "fees_tao": {"bits": 0}, + "fees_alpha": {"bits": 0}, + }, ], [ - (2,), - mocker.Mock( - value={ - "id": (2,), - "netuid": 2, - "tick_low": (216189,), - "tick_high": (198196,), - "liquidity": 2000000000000, - "fees_tao": {"bits": 0}, - "fees_alpha": {"bits": 0}, - } - ), + 2, + { + "id": (2,), + "netuid": 2, + "tick_low": (216189,), + "tick_high": (198196,), + "liquidity": 2000000000000, + "fees_tao": {"bits": 0}, + "fees_alpha": {"bits": 0}, + }, ], [ - (2,), - mocker.Mock( - value={ - "id": (2,), - "netuid": 2, - "tick_low": (226189,), - "tick_high": (188196,), - "liquidity": 3000000000000, - "fees_tao": {"bits": 0}, - "fees_alpha": {"bits": 0}, - } - ), + 2, + { + "id": (2,), + "netuid": 2, + "tick_low": (226189,), + "tick_high": (188196,), + "liquidity": 3000000000000, + "fees_tao": {"bits": 0}, + "fees_alpha": {"bits": 0}, + }, ], ] @@ -4213,8 +4168,8 @@ async def test_get_auto_stakes(subtensor, mocker): fake_hk_1 = mocker.Mock() fake_hk_2 = mocker.Mock() - dest_value_1 = mocker.Mock(value=[fake_hk_1]) - dest_value_2 = mocker.Mock(value=[fake_hk_2]) + dest_value_1 = fake_hk_1 + dest_value_2 = fake_hk_2 mock_result = mocker.MagicMock() mock_result.__aiter__.return_value = iter([(0, dest_value_1), (1, dest_value_2)]) @@ -4222,12 +4177,6 @@ async def test_get_auto_stakes(subtensor, mocker): subtensor.substrate, "query_map", return_value=mock_result ) - mocked_decode_account_id = mocker.patch.object( - async_subtensor, - "decode_account_id", - side_effect=[fake_hk_1, fake_hk_2], - ) - # Call result = await subtensor.get_auto_stakes(coldkey_ss58=fake_coldkey) @@ -4239,9 +4188,6 @@ async def test_get_auto_stakes(subtensor, mocker): params=[fake_coldkey], block_hash=mock_determine_block_hash.return_value, ) - mocked_decode_account_id.assert_has_calls( - [mocker.call(dest_value_1.value[0]), mocker.call(dest_value_2.value[0])] - ) assert result == {0: fake_hk_1, 1: fake_hk_2} @@ -4639,7 +4585,6 @@ async def test_get_crowdloan_contributions(mocker, subtensor): subtensor.substrate, "query_map", return_value=fake_result ) - mocked_decode_account_id = mocker.patch.object(async_subtensor, "decode_account_id") mocked_from_rao = mocker.patch.object(async_subtensor.Balance, "from_rao") # Call @@ -4653,9 +4598,7 @@ async def test_get_crowdloan_contributions(mocker, subtensor): params=[fake_crowdloan_id], block_hash=mocked_determine_block_hash.return_value, ) - assert result == { - mocked_decode_account_id.return_value: mocked_from_rao.return_value - } + assert result == {fake_hk_array: mocked_from_rao.return_value} @pytest.mark.parametrize( @@ -4724,7 +4667,7 @@ async def test_get_crowdloans(mocker, subtensor): """Tests subtensor `get_crowdloans` method.""" # Preps fake_id = mocker.Mock(spec=int) - fake_crowdloan = mocker.Mock(value=mocker.Mock(spec=dict)) + fake_crowdloan = mocker.Mock(spec=dict) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") records = [(fake_id, fake_crowdloan)] @@ -4753,7 +4696,7 @@ async def test_get_crowdloans(mocker, subtensor): ) mocked_decode_crowdloan_entry.assert_awaited_once_with( crowdloan_id=fake_id, - data=fake_crowdloan.value, + data=fake_crowdloan, block_hash=mocked_determine_block_hash.return_value, ) assert result == [mocked_decode_crowdloan_entry.return_value] @@ -5807,10 +5750,10 @@ async def test_get_stake_info_for_coldkeys_success(subtensor, mocker): fake_block_hash = None fake_reuse_block = False - fake_ck1 = b"\x16:\xech\r\xde,g\x03R1\xb9\x88q\xe79\xb8\x88\x93\xae\xd2)?*\rp\xb2\xe62\xads\x1c" - fake_ck2 = b"\x17:\xech\r\xde,g\x03R1\xb9\x88q\xe79\xb8\x88\x93\xae\xd2)?*\rp\xb2\xe62\xads\x1d" - fake_decoded_ck1 = "decoded_coldkey1" - fake_decoded_ck2 = "decoded_coldkey2" + fake_ck1 = fake_coldkey_ss58s[0] + fake_ck2 = fake_coldkey_ss58s[1] + fake_decoded_ck1 = fake_coldkey_ss58s[0] + fake_decoded_ck2 = fake_coldkey_ss58s[1] stake_info_dict_1 = { "netuid": 1, @@ -5843,12 +5786,6 @@ async def test_get_stake_info_for_coldkeys_success(subtensor, mocker): ) subtensor.query_runtime_api = mocked_query_runtime_api - mocked_decode_account_id = mocker.patch.object( - async_subtensor, - "decode_account_id", - side_effect=[fake_decoded_ck1, fake_decoded_ck2], - ) - mock_stake_info_1 = mocker.Mock(spec=StakeInfo) mock_stake_info_2 = mocker.Mock(spec=StakeInfo) mocked_stake_info_list_from_dicts = mocker.patch.object( @@ -5878,9 +5815,6 @@ async def test_get_stake_info_for_coldkeys_success(subtensor, mocker): block_hash=fake_block_hash, reuse_block=fake_reuse_block, ) - mocked_decode_account_id.assert_has_calls( - [mocker.call(fake_ck1), mocker.call(fake_ck2)] - ) mocked_stake_info_list_from_dicts.assert_has_calls( [mocker.call([stake_info_dict_1]), mocker.call([stake_info_dict_2])] ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 3e63a7be24..436b39b289 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -912,10 +912,6 @@ def test_query_runtime_api(subtensor, mocker): subtensor, "determine_block_hash", ) - # mock_runtime_call = mocker.patch.object( - # subtensor.substrate, - # "runtime_call", - # ) # Call result = subtensor.query_runtime_api(fake_runtime_api, fake_method, None) @@ -929,7 +925,7 @@ def test_query_runtime_api(subtensor, mocker): ) mock_determine_block_hash.assert_called_once_with(None) - assert result == subtensor.substrate.runtime_call.return_value.value + assert result == subtensor.substrate.runtime_call.return_value def test_query_map_subtensor(subtensor, mocker): @@ -1372,7 +1368,7 @@ def test_neuron_for_uid_response_none(subtensor, mocker): subtensor_module.NeuronInfo, "get_null_neuron" ) - subtensor.substrate.runtime_call.return_value.value = None + subtensor.substrate.runtime_call.return_value = None # Call result = subtensor.neuron_for_uid( @@ -1502,10 +1498,7 @@ def test_get_commitment(subtensor, mocker): fake_uid = 2 fake_block = 3 fake_hotkey = "hotkey" - expected_result = ( - "{'peer_id': '12D3KooWFWnHBmUFxvfL6PfZ5eGHdhgsEqNnsxuN1HE9EtfW8THi', " - "'model_huggingface_id': 'kmfoda/gpt2-1b-miner-3'}" - ) + expected_result = "{'peer_id': '12D3KooWRNw54AW4zrQWeZL2buhU8P71gfo9PXQQAHUwEAe4hA34', 'model_huggingface_id': None}" mocked_metagraph = mocker.MagicMock() subtensor.metagraph = mocked_metagraph @@ -1516,133 +1509,11 @@ def test_get_commitment(subtensor, mocker): "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, - ), - ) - }, - ), - ) + "fields": [ + { + "Raw97": "0x7b27706565725f6964273a2027313244334b6f6f57524e7735344157347a725157655a4c32627568553850373167666f3950585151414855774541653468413334272c20276d6f64656c5f68756767696e67666163655f6964273a204e6f6e657d" + } + ], }, } @@ -4161,10 +4032,8 @@ def test_all_subnets(subtensor, mocker): "get_subnet_prices", return_value={0: Balance.from_tao(1), 1: Balance.from_tao(0.029258617)}, ) - mocked_decode = mocker.Mock(return_value=[{"netuid": 0}, {"netuid": 1}]) - mocked_runtime_call = mocker.Mock(decode=mocked_decode) mocker.patch.object( - subtensor.substrate, "runtime_call", return_value=mocked_runtime_call + subtensor.substrate, "runtime_call", return_value=[{"netuid": 0}, {"netuid": 1}] ) # Call @@ -4357,6 +4226,7 @@ def test_get_timelocked_weight_commits(subtensor, mocker): storage_function="TimelockedWeightCommits", params=[netuid], block_hash=mock_determine_block_hash.return_value, + page_size=1, ) assert result == [] From ba9ae10a88edb556e4f8968c4f848e77225e77a8 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 16:58:12 +0200 Subject: [PATCH 06/38] Bump cyscale --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e98ce292fb..7ddaf7e5ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "10.2.0" description = "Bittensor SDK" readme = "README.md" authors = [ - {name = "bittensor.com"} + { name = "bittensor.com" } ] license = { file = "LICENSE" } requires-python = ">=3.10,<3.15" @@ -29,7 +29,7 @@ dependencies = [ "retry==0.9.2", "requests>=2.0.0,<3.0", "pydantic>=2.3,<3", - "cyscale==0.1.11", + "cyscale==0.2.0", "uvicorn", "bittensor-drand>=1.3.0,<2.0.0", "bittensor-wallet==4.0.1", @@ -94,5 +94,5 @@ classifiers = [ ] [tool.setuptools] -package-dir = {"bittensor" = "bittensor"} +package-dir = { "bittensor" = "bittensor" } script-files = ["bittensor/utils/certifi.sh"] From 75ef5c609522ebf01edc61cfacf49e38562e61ee Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 17:40:48 +0200 Subject: [PATCH 07/38] Checkin --- bittensor/core/chain_data/__init__.py | 3 +- bittensor/core/chain_data/coldkey_swap.py | 3 +- bittensor/core/chain_data/crowdloan_info.py | 11 +--- bittensor/core/chain_data/delegate_info.py | 11 ++-- .../core/chain_data/delegate_info_lite.py | 5 +- bittensor/core/chain_data/dynamic_info.py | 5 +- bittensor/core/chain_data/metagraph_info.py | 42 +++---------- bittensor/core/chain_data/neuron_info.py | 6 +- bittensor/core/chain_data/neuron_info_lite.py | 6 +- .../core/chain_data/proposal_vote_data.py | 5 +- bittensor/core/chain_data/stake_info.py | 5 +- bittensor/core/chain_data/subnet_info.py | 3 +- bittensor/core/chain_data/subnet_state.py | 5 +- bittensor/core/chain_data/utils.py | 22 +------ .../core/chain_data/weight_commit_info.py | 12 ++-- bittensor/core/subtensor.py | 28 ++++----- tests/unit_tests/test_async_subtensor.py | 30 ---------- tests/unit_tests/test_subtensor.py | 59 ++----------------- 18 files changed, 58 insertions(+), 203 deletions(-) diff --git a/bittensor/core/chain_data/__init__.py b/bittensor/core/chain_data/__init__.py index 0982c5cde2..f66c3bd0a6 100644 --- a/bittensor/core/chain_data/__init__.py +++ b/bittensor/core/chain_data/__init__.py @@ -37,7 +37,7 @@ from .subnet_identity import SubnetIdentity from .subnet_info import SubnetInfo from .subnet_state import SubnetState -from .utils import decode_account_id, process_stake_data +from .utils import process_stake_data from .weight_commit_info import WeightCommitInfo ProposalCallData = GenericCall @@ -78,6 +78,5 @@ "SubnetInfo", "SubnetState", "WeightCommitInfo", - "decode_account_id", "process_stake_data", ] diff --git a/bittensor/core/chain_data/coldkey_swap.py b/bittensor/core/chain_data/coldkey_swap.py index fb64df43c5..6388ce452b 100644 --- a/bittensor/core/chain_data/coldkey_swap.py +++ b/bittensor/core/chain_data/coldkey_swap.py @@ -1,7 +1,6 @@ from dataclasses import asdict, dataclass, fields from typing import Optional from scalecodec.base import ScaleType -from bittensor.core.chain_data.utils import decode_account_id @dataclass @@ -67,7 +66,7 @@ def from_record(cls, record: tuple) -> "ColdkeySwapAnnouncementInfo": Returns: ColdkeySwapAnnouncementInfo object with announcement details for the coldkey from the record. """ - coldkey_ss58 = decode_account_id(record[0]) + coldkey_ss58 = record[0] announcement_data = record[1] return cls.from_query(coldkey_ss58, announcement_data) diff --git a/bittensor/core/chain_data/crowdloan_info.py b/bittensor/core/chain_data/crowdloan_info.py index 83e119ffbd..db7185b78d 100644 --- a/bittensor/core/chain_data/crowdloan_info.py +++ b/bittensor/core/chain_data/crowdloan_info.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from typing import Optional -from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils.balance import Balance @@ -47,18 +46,14 @@ def from_dict(cls, idx: int, data: dict) -> "CrowdloanInfo": """Returns a CrowdloanInfo object from decoded chain data.""" return cls( id=idx, - creator=decode_account_id(data["creator"]), + creator=data["creator"], deposit=Balance.from_rao(data["deposit"]), min_contribution=Balance.from_rao(data["min_contribution"]), end=data["end"], cap=Balance.from_rao(data["cap"]), - funds_account=decode_account_id(data["funds_account"]) - if data.get("funds_account") - else None, + funds_account=data["funds_account"], raised=Balance.from_rao(data["raised"]), - target_address=decode_account_id(data.get("target_address")) - if data.get("target_address") - else None, + target_address=data.get("target_address"), call=data.get("call") if data.get("call") else None, finalized=data["finalized"], contributors_count=data["contributors_count"], diff --git a/bittensor/core/chain_data/delegate_info.py b/bittensor/core/chain_data/delegate_info.py index 91301b6034..d12696deb8 100644 --- a/bittensor/core/chain_data/delegate_info.py +++ b/bittensor/core/chain_data/delegate_info.py @@ -2,7 +2,6 @@ from typing import Optional from bittensor.core.chain_data.info_base import InfoBase -from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -47,14 +46,14 @@ class DelegateInfo(DelegateInfoBase): @classmethod def _from_dict(cls, decoded: dict) -> Optional["DelegateInfo"]: - hotkey = decode_account_id(decoded.get("delegate_ss58")) - owner = decode_account_id(decoded.get("owner_ss58")) + hotkey = decoded.get("delegate_ss58") + owner = decoded.get("owner_ss58") nominators = {} total_stake_by_netuid = {} for raw_nominator, raw_stakes in decoded.get("nominators", []): - nominator_ss58 = decode_account_id(raw_nominator) + nominator_ss58 = raw_nominator stakes = { int(netuid): Balance.from_rao(stake_amt).set_unit(int(netuid)) for (netuid, stake_amt) in raw_stakes @@ -96,8 +95,8 @@ def _from_dict( cls, decoded: tuple[dict, tuple[int, int]] ) -> Optional["DelegatedInfo"]: delegate_info, (netuid, stake) = decoded - hotkey = decode_account_id(delegate_info.get("delegate_ss58")) - owner = decode_account_id(delegate_info.get("owner_ss58")) + hotkey = delegate_info.get("delegate_ss58") + owner = delegate_info.get("owner_ss58") return cls( hotkey_ss58=hotkey, owner_ss58=owner, diff --git a/bittensor/core/chain_data/delegate_info_lite.py b/bittensor/core/chain_data/delegate_info_lite.py index 06666769dc..262c708619 100644 --- a/bittensor/core/chain_data/delegate_info_lite.py +++ b/bittensor/core/chain_data/delegate_info_lite.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from bittensor.core.chain_data.info_base import InfoBase -from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -34,10 +33,10 @@ class DelegateInfoLite(InfoBase): @classmethod def _from_dict(cls, decoded: dict) -> "DelegateInfoLite": return DelegateInfoLite( - delegate_ss58=decode_account_id(decoded["delegate_ss58"]), + delegate_ss58=decoded["delegate_ss58"], take=u16_normalized_float(decoded["take"]), nominators=decoded["nominators"], - owner_ss58=decode_account_id(decoded["owner_ss58"]), + owner_ss58=decoded["owner_ss58"], registrations=decoded["registrations"], validator_permits=decoded["validator_permits"], return_per_1000=Balance.from_rao(decoded["return_per_1000"]), diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index 4d80d79dce..b92f9fa816 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -7,7 +7,6 @@ from typing import Optional, Union from bittensor.core.chain_data.info_base import InfoBase -from bittensor.core.chain_data.utils import decode_account_id from bittensor.core.chain_data.subnet_identity import SubnetIdentity from bittensor.utils.balance import Balance, fixed_to_float @@ -52,8 +51,8 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo": True if int(decoded["netuid"]) > 0 else False ) # Root is not dynamic - owner_hotkey = decode_account_id(decoded["owner_hotkey"]) - owner_coldkey = decode_account_id(decoded["owner_coldkey"]) + owner_hotkey = decoded["owner_hotkey"] + owner_coldkey = decoded["owner_coldkey"] emission = Balance.from_rao(decoded["emission"]).set_unit(0) alpha_in = Balance.from_rao(decoded["alpha_in"]).set_unit(netuid) diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index d82292fb1e..989bfd6861 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -6,7 +6,6 @@ from bittensor.core.chain_data.chain_identity import ChainIdentity 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 ( get_netuid_and_mechid_by_storage_index, u64_normalized_float as u64tf, @@ -25,8 +24,7 @@ def get_selective_metagraph_commitments( if commitments := decoded.get("commitments"): result = [] for commitment in commitments: - account_id_bytes, commitment_bytes = commitment - hotkey = decode_account_id(account_id_bytes) + hotkey, commitment_bytes = commitment commitment = bytes( commitment_bytes[SELECTIVE_METAGRAPH_COMMITMENTS_OFFSET:] ).decode("utf-8", errors="ignore") @@ -209,16 +207,8 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": identity=decoded["identity"], network_registered_at=decoded["network_registered_at"], # Keys for owner. - owner_hotkey=( - decode_account_id(decoded["owner_hotkey"][0]) - if decoded.get("owner_hotkey") is not None - else None - ), - owner_coldkey=( - decode_account_id(decoded["owner_coldkey"][0]) - if decoded.get("owner_coldkey") is not None - else None - ), + owner_hotkey=decoded.get("owner_hotkey"), + owner_coldkey=decoded.get("owner_coldkey"), # Tempo terms. block=decoded["block"], tempo=decoded["tempo"], @@ -312,16 +302,8 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": else None ), # Metagraph info. - hotkeys=( - [decode_account_id(ck) for ck in decoded.get("hotkeys", [])] - if decoded.get("hotkeys") is not None - else None - ), - coldkeys=( - [decode_account_id(hk) for hk in decoded.get("coldkeys", [])] - if decoded.get("coldkeys") is not None - else None - ), + hotkeys=decoded.get("hotkeys"), + coldkeys=decoded.get("coldkeys"), identities=decoded["identities"], axons=decoded.get("axons", []), active=decoded["active"], @@ -383,19 +365,13 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": ), # Dividend break down tao_dividends_per_hotkey=( - [ - (decode_account_id(alpha[0]), _tbwu(alpha[1])) - for alpha in decoded["tao_dividends_per_hotkey"] - ] - if decoded.get("tao_dividends_per_hotkey") is not None + [(ss58, _tbwu(alpha)) for (ss58, alpha) in tdph] + if (tdph := decoded.get("tao_dividends_per_hotkey")) is not None else None ), alpha_dividends_per_hotkey=( - [ - (decode_account_id(adphk[0]), _tbwu(adphk[1], _netuid)) - for adphk in decoded["alpha_dividends_per_hotkey"] - ] - if decoded.get("alpha_dividends_per_hotkey") is not None + [(ss58, _tbwu(adphk, _netuid)) for (ss58, adphk) in adph] + if (adph := decoded.get("alpha_dividends_per_hotkey")) is not None else None ), validators=[v for v in decoded["validators"]] diff --git a/bittensor/core/chain_data/neuron_info.py b/bittensor/core/chain_data/neuron_info.py index 6c3b89293d..b9bb43fd94 100644 --- a/bittensor/core/chain_data/neuron_info.py +++ b/bittensor/core/chain_data/neuron_info.py @@ -4,7 +4,7 @@ from bittensor.core.chain_data.axon_info import AxonInfo from bittensor.core.chain_data.info_base import InfoBase from bittensor.core.chain_data.prometheus_info import PrometheusInfo -from bittensor.core.chain_data.utils import decode_account_id, process_stake_data +from bittensor.core.chain_data.utils import process_stake_data from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -120,8 +120,8 @@ def _from_dict(cls, decoded: Any) -> "NeuronInfo": """Returns a NeuronInfo object from decoded chain data.""" stake_dict = process_stake_data(decoded["stake"]) total_stake = sum(stake_dict.values()) if stake_dict else Balance(0) - coldkey = decode_account_id(decoded["coldkey"]) - hotkey = decode_account_id(decoded["hotkey"]) + coldkey = decoded["coldkey"] + hotkey = decoded["hotkey"] return NeuronInfo( active=decoded["active"], axon_info=AxonInfo.from_dict( diff --git a/bittensor/core/chain_data/neuron_info_lite.py b/bittensor/core/chain_data/neuron_info_lite.py index e1d8a33048..7ab5e29f44 100644 --- a/bittensor/core/chain_data/neuron_info_lite.py +++ b/bittensor/core/chain_data/neuron_info_lite.py @@ -4,7 +4,7 @@ from bittensor.core.chain_data.axon_info import AxonInfo from bittensor.core.chain_data.info_base import InfoBase from bittensor.core.chain_data.prometheus_info import PrometheusInfo -from bittensor.core.chain_data.utils import decode_account_id, process_stake_data +from bittensor.core.chain_data.utils import process_stake_data from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -87,8 +87,8 @@ def get_null_neuron() -> "NeuronInfoLite": @classmethod def _from_dict(cls, decoded: Any) -> "NeuronInfoLite": """Returns a NeuronInfoLite object from decoded chain data.""" - coldkey = decode_account_id(decoded["coldkey"]) - hotkey = decode_account_id(decoded["hotkey"]) + coldkey = decoded["coldkey"] + hotkey = decoded["hotkey"] stake_dict = process_stake_data(decoded["stake"]) stake = sum(stake_dict.values()) if stake_dict else Balance(0) diff --git a/bittensor/core/chain_data/proposal_vote_data.py b/bittensor/core/chain_data/proposal_vote_data.py index 3cf5439955..98e221baa5 100644 --- a/bittensor/core/chain_data/proposal_vote_data.py +++ b/bittensor/core/chain_data/proposal_vote_data.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from bittensor.core.chain_data.info_base import InfoBase -from bittensor.core.chain_data.utils import decode_account_id @dataclass @@ -19,9 +18,9 @@ class ProposalVoteData(InfoBase): @classmethod def from_dict(cls, proposal_dict: dict) -> "ProposalVoteData": return cls( - ayes=[decode_account_id(key) for key in proposal_dict["ayes"]], + ayes=proposal_dict["ayes"], end=proposal_dict["end"], index=proposal_dict["index"], - nays=[decode_account_id(key) for key in proposal_dict["nays"]], + nays=proposal_dict["nays"], threshold=proposal_dict["threshold"], ) diff --git a/bittensor/core/chain_data/stake_info.py b/bittensor/core/chain_data/stake_info.py index 4f52ddfe2f..42d397c53f 100644 --- a/bittensor/core/chain_data/stake_info.py +++ b/bittensor/core/chain_data/stake_info.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from bittensor.core.chain_data.info_base import InfoBase -from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils.balance import Balance @@ -30,8 +29,8 @@ def from_dict(cls, decoded: dict) -> "StakeInfo": """Returns a StakeInfo object from decoded chain data.""" netuid = decoded["netuid"] return cls( - hotkey_ss58=decode_account_id(decoded["hotkey"]), - coldkey_ss58=decode_account_id(decoded["coldkey"]), + hotkey_ss58=decoded["hotkey"], + coldkey_ss58=decoded["coldkey"], netuid=int(netuid), stake=Balance.from_rao(decoded["stake"]).set_unit(netuid), locked=Balance.from_rao(decoded["locked"]).set_unit(netuid), diff --git a/bittensor/core/chain_data/subnet_info.py b/bittensor/core/chain_data/subnet_info.py index 978dab29f7..765dae6bb6 100644 --- a/bittensor/core/chain_data/subnet_info.py +++ b/bittensor/core/chain_data/subnet_info.py @@ -2,7 +2,6 @@ from typing import Any from bittensor.core.chain_data.info_base import InfoBase -from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -50,7 +49,7 @@ def _from_dict(cls, decoded: Any) -> "SubnetInfo": min_allowed_weights=decoded["min_allowed_weights"], modality=decoded["network_modality"], netuid=decoded["netuid"], - owner_ss58=decode_account_id(decoded["owner"]), + owner_ss58=decoded["owner"], rho=decoded["rho"], scaling_law_power=decoded["scaling_law_power"], subnetwork_n=decoded["subnetwork_n"], diff --git a/bittensor/core/chain_data/subnet_state.py b/bittensor/core/chain_data/subnet_state.py index 95a38536e3..b554dae6f4 100644 --- a/bittensor/core/chain_data/subnet_state.py +++ b/bittensor/core/chain_data/subnet_state.py @@ -6,7 +6,6 @@ from dataclasses import dataclass from bittensor.core.chain_data.info_base import InfoBase -from bittensor.core.chain_data.utils import decode_account_id from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -38,8 +37,8 @@ def _from_dict(cls, decoded: dict) -> "SubnetState": netuid = decoded["netuid"] return SubnetState( netuid=netuid, - hotkeys=[decode_account_id(hk) for hk in decoded.get("hotkeys", [])], - coldkeys=[decode_account_id(ck) for ck in decoded.get("coldkeys", [])], + hotkeys=decoded.get("hotkeys", []), + coldkeys=decoded.get("coldkeys", []), active=decoded["active"], validator_permit=decoded["validator_permit"], pruning_score=[ diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index 6c318b172b..2d991319b3 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -3,11 +3,9 @@ from enum import Enum from typing import Optional, Union, TYPE_CHECKING -from bittensor_wallet.utils import SS58_FORMAT from scalecodec import ScaleBytes from scalecodec.base import RuntimeConfiguration from scalecodec.type_registry import load_type_registry_preset -from scalecodec.utils.ss58 import ss58_encode from bittensor.utils.balance import Balance @@ -101,23 +99,6 @@ def from_scale_encoding_using_type_string( return obj.decode() -def decode_account_id(account_id_bytes: Union[bytes, str]) -> str: - """ - Decodes an AccountId from bytes to a Base64 string using SS58 encoding. - - Parameters: - account_id_bytes: The AccountId in bytes that needs to be decoded. - - Returns: - str: The decoded AccountId as a Base64 string. - """ - if isinstance(account_id_bytes, tuple) and isinstance(account_id_bytes[0], tuple): - account_id_bytes = account_id_bytes[0] - - # Convert the AccountId bytes to a Base64 string - return ss58_encode(bytes(account_id_bytes).hex(), SS58_FORMAT) - - def process_stake_data(stake_data: list) -> dict: """ Processes stake data to decode account IDs and convert stakes from rao to Balance objects. @@ -129,8 +110,7 @@ def process_stake_data(stake_data: list) -> dict: dict: A dictionary with account IDs as keys and their corresponding Balance objects as values. """ decoded_stake_data = {} - for account_id_bytes, stake_ in stake_data: - account_id = decode_account_id(account_id_bytes) + for account_id, stake_ in stake_data: decoded_stake_data.update({account_id: Balance.from_rao(stake_)}) return decoded_stake_data diff --git a/bittensor/core/chain_data/weight_commit_info.py b/bittensor/core/chain_data/weight_commit_info.py index 3e6f13517a..0c917c740e 100644 --- a/bittensor/core/chain_data/weight_commit_info.py +++ b/bittensor/core/chain_data/weight_commit_info.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -from bittensor.core.chain_data.utils import decode_account_id from typing import Optional @@ -35,13 +34,12 @@ def from_vec_u8(cls, data: tuple) -> tuple[str, str, int]: This method is used when querying a block or block hash where storage functions `CRV3WeightCommitsV2` does not exist in Subtensor module. """ - account_id, commit_block, commit_data, round_number = data - - account_id_ = account_id[0] if isinstance(account_id, tuple) else account_id - commit_data = commit_data[0] if isinstance(commit_data, tuple) else commit_data - commit_hex = "0x" + "".join(format(x, "02x") for x in commit_data) + account_id: str + commit_hex: str + round_number: int + account_id, commit_hex, round_number = data - return decode_account_id(account_id_), commit_hex, round_number + return account_id, commit_hex, round_number @classmethod def from_vec_u8_v2(cls, data: tuple) -> tuple[str, int, str, int]: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e47759551f..82fef7b20b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -38,7 +38,6 @@ SubnetIdentity, SubnetInfo, WeightCommitInfo, - decode_account_id, ) from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.utils import ( @@ -1325,8 +1324,7 @@ def get_auto_stakes( ) pairs = {} - for netuid, destination in query: - hotkey_ss58 = decode_account_id(destination.value[0]) + for netuid, hotkey_ss58 in query: if hotkey_ss58: pairs[int(netuid)] = hotkey_ss58 @@ -1560,7 +1558,7 @@ def get_children_pending( [ ( u64_normalized_float(proportion), - decode_account_id(child[0]), + child, ) for proportion, child in children ], @@ -1941,9 +1939,9 @@ def get_crowdloan_contributions( block_hash=block_hash, ) result = {} - for record in query.records: - if record[1].value: - result[decode_account_id(record[0])] = Balance.from_rao(record[1].value) + for contributor, amount in query.records: + if amount: + result[contributor] = Balance.from_rao(amount) return result def get_crowdloan_by_id( @@ -2106,8 +2104,8 @@ def get_delegate_identities( ) return { - decode_account_id(ss58_address[0]): ChainIdentity.from_dict( - decode_hex_identity_dict(identity.value), + ss58_address: ChainIdentity.from_dict( + decode_hex_identity_dict(identity), ) for ss58_address, identity in identities } @@ -2934,7 +2932,7 @@ def get_owned_hotkeys( params=[coldkey_ss58], block_hash=block_hash, ) - return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] + return owned_hotkeys def get_parents( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None @@ -2963,9 +2961,8 @@ def get_parents( ) if parents: formatted_parents = [] - for proportion, parent in parents.value: + for proportion, formatted_child in parents.value: # Convert U64 to int - formatted_child = decode_account_id(parent[0]) normalized_proportion = u64_normalized_float(proportion) formatted_parents.append((normalized_proportion, formatted_child)) return formatted_parents @@ -3561,10 +3558,7 @@ def get_stake_info_for_coldkeys( if query is None: return {} - return { - decode_account_id(ck): StakeInfo.list_from_dicts(st_info) - for ck, st_info in query - } + return {ck: StakeInfo.list_from_dicts(st_info) for ck, st_info in query} def get_stake_for_hotkey( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None @@ -3677,7 +3671,7 @@ def get_staking_hotkeys( params=[coldkey_ss58], block_hash=self.determine_block_hash(block), ) - return [decode_account_id(hotkey[0]) for hotkey in result or []] + return result or [] def get_start_call_delay(self, block: Optional[int] = None) -> int: """ diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 21ba4bb12b..8a24fd0d45 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -15,7 +15,6 @@ NeuronInfo, SelectiveMetagraphIndex, StakeInfo, - proposal_vote_data, ) from bittensor.core.errors import BalanceTypeError from bittensor.core.settings import DEFAULT_MEV_PROTECTION, DEFAULT_PERIOD @@ -40,35 +39,6 @@ def subtensor(mock_substrate): return async_subtensor.AsyncSubtensor() -def test_decode_ss58_tuples_in_proposal_vote_data(mocker): - """Tests that ProposalVoteData instance instantiation works properly,""" - # Preps - mocked_decode_account_id = mocker.patch.object( - proposal_vote_data, "decode_account_id" - ) - fake_proposal_dict = { - "index": "0", - "threshold": 1, - "ayes": ("0 line", "1 line"), - "nays": ("2 line", "3 line"), - "end": 123, - } - - # Call - async_subtensor.ProposalVoteData.from_dict(fake_proposal_dict) - - # Asserts - assert mocked_decode_account_id.call_count == len(fake_proposal_dict["ayes"]) + len( - fake_proposal_dict["nays"] - ) - assert mocked_decode_account_id.mock_calls == [ - mocker.call("0 line"), - mocker.call("1 line"), - mocker.call("2 line"), - mocker.call("3 line"), - ] - - 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"} diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 436b39b289..8a8d5a8c59 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2009,11 +2009,6 @@ def test_does_hotkey_exist_true(mocker, subtensor): "query", return_value=mocker.Mock(value=[fake_owner]), ) - mocker.patch.object( - subtensor_module, - "decode_account_id", - return_value=fake_owner, - ) # Call result = subtensor.does_hotkey_exist(fake_hotkey_ss58, block=fake_block) @@ -2067,12 +2062,6 @@ def test_does_hotkey_exist_special_id(mocker, subtensor): "query", return_value=fake_owner, ) - mocker.patch.object( - subtensor_module, - "decode_account_id", - return_value=fake_owner, - ) - # Call result = subtensor.does_hotkey_exist(fake_hotkey_ss58, block=fake_block) @@ -2099,11 +2088,6 @@ def test_does_hotkey_exist_latest_block(mocker, subtensor): "query", return_value=mocker.Mock(value=[fake_owner]), ) - mocker.patch.object( - subtensor_module, - "decode_account_id", - return_value=fake_owner, - ) # Call result = subtensor.does_hotkey_exist(fake_hotkey_ss58) @@ -2194,11 +2178,6 @@ def test_get_hotkey_owner_does_not_exist(mocker, subtensor): mock_does_hotkey_exist = mocker.patch.object( subtensor, "does_hotkey_exist", return_value=False ) - mocker.patch.object( - subtensor_module, - "decode_account_id", - return_value=fake_hotkey_ss58, - ) # Call result = subtensor.get_hotkey_owner(fake_hotkey_ss58, block=fake_block) @@ -2980,18 +2959,11 @@ def test_get_owned_hotkeys_happy_path(subtensor, mocker): fake_coldkey = "fake_hotkey" fake_hotkey = "fake_hotkey" fake_hotkeys = [ - [ - fake_hotkey, - ] + fake_hotkey, ] mocked_subtensor = mocker.Mock(return_value=fake_hotkeys) mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor) - mocked_decode_account_id = mocker.Mock() - mocker.patch.object( - subtensor_module, "decode_account_id", new=mocked_decode_account_id - ) - # Call result = subtensor.get_owned_hotkeys(fake_coldkey) @@ -3002,8 +2974,7 @@ def test_get_owned_hotkeys_happy_path(subtensor, mocker): params=[fake_coldkey], block_hash=None, ) - assert result == [mocked_decode_account_id.return_value] - mocked_decode_account_id.assert_called_once_with(fake_hotkey) + assert result == fake_hotkeys def test_get_owned_hotkeys_return_empty(subtensor, mocker): @@ -3586,19 +3557,14 @@ def test_get_parents_success(subtensor, mocker): fake_netuid = 1 fake_parents = mocker.Mock( value=[ - (1000, ["parent_key_1"]), - (2000, ["parent_key_2"]), + (1000, "decoded_parent_key_1"), + (2000, "decoded_parent_key_2"), ] ) mocked_query = mocker.MagicMock(return_value=fake_parents) subtensor.substrate.query = mocked_query - mocked_decode_account_id = mocker.Mock( - side_effect=["decoded_parent_key_1", "decoded_parent_key_2"] - ) - mocker.patch.object(subtensor_module, "decode_account_id", mocked_decode_account_id) - expected_formatted_parents = [ (u64_normalized_float(1000), "decoded_parent_key_1"), (u64_normalized_float(2000), "decoded_parent_key_2"), @@ -3614,9 +3580,6 @@ def test_get_parents_success(subtensor, mocker): storage_function="ParentKeys", params=[fake_hotkey, fake_netuid], ) - mocked_decode_account_id.assert_has_calls( - [mocker.call("parent_key_1"), mocker.call("parent_key_2")] - ) assert result == expected_formatted_parents @@ -4358,21 +4321,12 @@ def test_get_auto_stakes(subtensor, mocker): fake_hk_1 = mocker.Mock() fake_hk_2 = mocker.Mock() - dest_value_1 = mocker.Mock(value=[fake_hk_1]) - dest_value_2 = mocker.Mock(value=[fake_hk_2]) - mock_result = mocker.MagicMock() - mock_result.__iter__.return_value = iter([(0, dest_value_1), (1, dest_value_2)]) + mock_result.__iter__.return_value = iter([(0, fake_hk_1), (1, fake_hk_2)]) mocked_query_map = mocker.patch.object( subtensor.substrate, "query_map", return_value=mock_result ) - mocked_decode_account_id = mocker.patch.object( - subtensor_module, - "decode_account_id", - side_effect=[fake_hk_1, fake_hk_2], - ) - # Call result = subtensor.get_auto_stakes(coldkey_ss58=fake_coldkey) @@ -4384,9 +4338,6 @@ def test_get_auto_stakes(subtensor, mocker): params=[fake_coldkey], block_hash=mock_determine_block_hash.return_value, ) - mocked_decode_account_id.assert_has_calls( - [mocker.call(dest_value_1.value[0]), mocker.call(dest_value_2.value[0])] - ) assert result == {0: fake_hk_1, 1: fake_hk_2} From 7ea1d1ba040722f451517ab06be647befce994fe Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 17:44:32 +0200 Subject: [PATCH 08/38] Removed decode_account_id --- tests/unit_tests/test_subtensor.py | 34 +++++++++--------------------- 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 8a8d5a8c59..3d34d2980f 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4676,16 +4676,13 @@ def test_get_crowdloan_constants(mocker, subtensor): def test_get_crowdloan_contributions(mocker, subtensor): """Tests subtensor `get_crowdloan_contributions` method.""" # Preps - fake_hk_array = mocker.Mock(spec=list) - fake_contribution = mocker.Mock(value=mocker.Mock(spec=Balance)) + fake_hk = mocker.Mock(spec=str) + fake_contribution = mocker.Mock(spec=int) fake_crowdloan_id = mocker.Mock(spec=int) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_query_map = mocker.patch.object(subtensor.substrate, "query_map") - mocked_query_map.return_value.records = [(fake_hk_array, fake_contribution)] - mocked_decode_account_id = mocker.patch.object( - subtensor_module, "decode_account_id" - ) + mocked_query_map.return_value.records = [(fake_hk, fake_contribution)] mocked_from_rao = mocker.patch.object(subtensor_module.Balance, "from_rao") # Call @@ -4693,9 +4690,7 @@ def test_get_crowdloan_contributions(mocker, subtensor): # Asserts mocked_determine_block_hash.assert_called_once() - assert result == { - mocked_decode_account_id.return_value: mocked_from_rao.return_value - } + assert result == {fake_hk: mocked_from_rao.return_value} @pytest.mark.parametrize( @@ -5797,14 +5792,14 @@ def test_get_stake_info_for_coldkeys_success(subtensor, mocker): fake_coldkey_ss58s = ["coldkey1", "coldkey2"] fake_block = 123 - fake_ck1 = b"\x16:\xech\r\xde,g\x03R1\xb9\x88q\xe79\xb8\x88\x93\xae\xd2)?*\rp\xb2\xe62\xads\x1c" - fake_ck2 = b"\x17:\xech\r\xde,g\x03R1\xb9\x88q\xe79\xb8\x88\x93\xae\xd2)?*\rp\xb2\xe62\xads\x1d" - fake_decoded_ck1 = "decoded_coldkey1" - fake_decoded_ck2 = "decoded_coldkey2" + fake_ck1 = "decoded_coldkey1" + fake_ck2 = "decoded_coldkey2" + fake_decoded_ck1 = fake_ck1 + fake_decoded_ck2 = fake_ck2 stake_info_dict_1 = { "netuid": 5, - "hotkey": b"\x16:\xech\r\xde,g\x03R1\xb9\x88q\xe79\xb8\x88\x93\xae\xd2)?*\rp\xb2\xe62\xads\x1c", + "hotkey": "fake_hk", "coldkey": fake_ck1, "stake": 1000, "locked": 0, @@ -5814,7 +5809,7 @@ def test_get_stake_info_for_coldkeys_success(subtensor, mocker): } stake_info_dict_2 = { "netuid": 14, - "hotkey": b"\x17:\xech\r\xde,g\x03R1\xb9\x88q\xe79\xb8\x88\x93\xae\xd2)?*\rp\xb2\xe62\xads\x1d", + "hotkey": "fake_hk", "coldkey": fake_ck2, "stake": 2000, "locked": 0, @@ -5832,12 +5827,6 @@ def test_get_stake_info_for_coldkeys_success(subtensor, mocker): subtensor, "query_runtime_api", return_value=fake_query_result ) - mocked_decode_account_id = mocker.patch.object( - subtensor_module, - "decode_account_id", - side_effect=[fake_decoded_ck1, fake_decoded_ck2], - ) - mock_stake_info_1 = mocker.Mock(spec=StakeInfo) mock_stake_info_2 = mocker.Mock(spec=StakeInfo) mocked_stake_info_list_from_dicts = mocker.patch.object( @@ -5862,9 +5851,6 @@ def test_get_stake_info_for_coldkeys_success(subtensor, mocker): params=[fake_coldkey_ss58s], block=fake_block, ) - mocked_decode_account_id.assert_has_calls( - [mocker.call(fake_ck1), mocker.call(fake_ck2)] - ) mocked_stake_info_list_from_dicts.assert_has_calls( [mocker.call([stake_info_dict_1]), mocker.call([stake_info_dict_2])] ) From 9457319b2716ee58f1b9751e89f62f20c9606308 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 18:46:30 +0200 Subject: [PATCH 09/38] Checkin --- bittensor/core/async_subtensor.py | 6 +----- bittensor/core/subtensor.py | 18 +++++++----------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 7c3525011b..0cbda79b45 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -503,12 +503,8 @@ async def _runtime_method_exists( """ runtime = await self.substrate.init_runtime(block_hash=block_hash) if runtime.metadata_v15 is not None: - metadata_v15_value = runtime.metadata_v15.value() - apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]} try: - api_entry = apis[api] - methods = {entry["name"]: entry for entry in api_entry["methods"]} - _ = methods[method] + _ = runtime.runtime_api_map[method] return True except KeyError: return False diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 82fef7b20b..e5064a01af 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -394,12 +394,8 @@ def _runtime_method_exists(self, api: str, method: str, block_hash: str) -> bool """ runtime = self.substrate.init_runtime(block_hash=block_hash) if runtime.metadata_v15 is not None: - metadata_v15_value = runtime.metadata_v15.value() - apis = {entry["name"]: entry for entry in metadata_v15_value["apis"]} try: - api_entry = apis[api] - methods = {entry["name"]: entry for entry in api_entry["methods"]} - _ = methods[method] + _ = runtime.runtime_api_map[method] return True except KeyError: return False @@ -2547,10 +2543,10 @@ def get_mechanism_emission_split( params=[netuid], block_hash=block_hash, ) - if result is None or not hasattr(result, "value"): + if result is None: return None - - return [round(i / sum(result.value) * 100) for i in result.value] + total = sum(result.value) + return [round(i / total * 100) for i in result.value] def get_mechanism_count( self, @@ -2804,9 +2800,9 @@ def get_netuids_for_hotkey( ) netuids = [] if result.records: - for record in result: - if record[1].value: - netuids.append(record[0]) + for netuid, is_member in result: + if is_member: + netuids.append(netuid) return netuids def get_neuron_certificate( From b846bb61949726eeeabaa72bda4409b62fc1bda6 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 21:16:01 +0200 Subject: [PATCH 10/38] Types --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/axon.py | 3 +- bittensor/core/subtensor.py | 69 +++++++++++++++---------------- bittensor/core/types.py | 12 ++++++ bittensor/utils/balance.py | 18 +++----- bittensor/utils/liquidity.py | 4 +- pyproject.toml | 2 +- 7 files changed, 56 insertions(+), 54 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 0cbda79b45..d9e4ef51e2 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -13,6 +13,7 @@ from bittensor_wallet.utils import SS58_FORMAT from scalecodec import GenericCall from scalecodec.base import ScaleType +from scalecodec.utils.math import FixedPoint from bittensor.core.chain_data import ( ColdkeySwapAnnouncementInfo, @@ -160,7 +161,6 @@ ) from bittensor.utils.balance import ( Balance, - FixedPoint, check_balance_amount, fixed_to_float, ) diff --git a/bittensor/core/axon.py b/bittensor/core/axon.py index f7227d6233..0895cf708a 100644 --- a/bittensor/core/axon.py +++ b/bittensor/core/axon.py @@ -21,6 +21,7 @@ from fastapi.responses import JSONResponse from fastapi.routing import serialize_response from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint +from starlette.types import ASGIApp from starlette.requests import Request from starlette.responses import Response @@ -1097,7 +1098,7 @@ class AxonMiddleware(BaseHTTPMiddleware): such as response header updating and logging. """ - def __init__(self, app: "AxonMiddleware", axon: "Axon"): + def __init__(self, app: ASGIApp, axon: "Axon"): """ Initialize the AxonMiddleware class. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e5064a01af..28c97d3c31 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -11,6 +11,7 @@ from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT from scalecodec.base import ScaleType +from scalecodec.utils.math import FixedPoint from bittensor.core.axon import Axon from bittensor.core.chain_data import ( @@ -145,6 +146,7 @@ SubtensorMixin, UIDs, Weights, + PositionResponse, ) from bittensor.utils import ( Certificate, @@ -159,7 +161,6 @@ ) from bittensor.utils.balance import ( Balance, - FixedPoint, check_balance_amount, fixed_to_float, ) @@ -715,7 +716,7 @@ def query_module( name: str, params: Optional[list] = None, block: Optional[int] = None, - ) -> Optional[ScaleType]: + ) -> ScaleType: """Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This function is a generic query interface that allows for flexible and diverse data retrieval from various blockchain modules. Use this function for nonstandard queries to storage defined within the Bittensor @@ -769,7 +770,7 @@ def query_subtensor( name: str, params: Optional[list] = None, block: Optional[int] = None, - ) -> Optional[ScaleType]: + ) -> ScaleType[Any]: """Queries named storage from the Subtensor module on the Bittensor blockchain. Use this function for nonstandard queries to constants defined within the Bittensor blockchain, if these cannot @@ -1377,9 +1378,13 @@ def get_balances( ] batch_call = self.substrate.query_multi(calls, block_hash=block_hash) results = {} + key: StorageKey + val: dict for item in batch_call: - value = item[1] or {"data": {"free": 0}} - results.update({item[0].params[0]: Balance(value["data"]["free"])}) + key, val = item + value = val or {"data": {"free": 0}} + assert key.params is not None + results.update({key.params[0]: Balance(value["data"]["free"])}) return results def get_current_block(self) -> int: @@ -1653,8 +1658,7 @@ def get_coldkey_swap_announcement_delay( storage_function="ColdkeySwapAnnouncementDelay", block_hash=block_hash, ) - value = getattr(query, "value", query) - return cast(int, value) if value is not None else 0 + return cast(int, query.value) or 0 def get_coldkey_swap_constants( self, @@ -1728,8 +1732,7 @@ def get_coldkey_swap_reannouncement_delay( storage_function="ColdkeySwapReannouncementDelay", block_hash=block_hash, ) - value = getattr(query, "value", query) - return cast(int, value) if value is not None else 0 + return cast(int, query.value) or 0 def get_coldkey_swap_dispute( self, @@ -2414,13 +2417,12 @@ def get_liquidity_list( sqrt_price = fixed_to_float(sqrt_price_query[1]) current_tick = price_to_tick(sqrt_price**2) - positions_values: list[tuple[dict, int, int]] = [] + positions_values: list[tuple[PositionResponse, int, int]] = [] positions_storage_keys: list[StorageKey] = [] - for _, p in positions_response: - position = p.value - - tick_low_idx = position["tick_low"][0] - tick_high_idx = position["tick_high"][0] + position: PositionResponse + for _, position in positions_response: + tick_low_idx = position["tick_low"] + tick_high_idx = position["tick_high"] tick_low_sk = self.substrate.create_storage_key( pallet="Swap", @@ -2499,19 +2501,15 @@ def get_liquidity_list( positions.append( LiquidityPosition( - **{ - "id": position.get("id")[0], - "price_low": Balance.from_tao( - tick_to_price(position.get("tick_low")[0]) - ), - "price_high": Balance.from_tao( - tick_to_price(position.get("tick_high")[0]) - ), - "liquidity": Balance.from_rao(position.get("liquidity")), - "fees_tao": fees_tao, - "fees_alpha": fees_alpha, - "netuid": position.get("netuid"), - } + id=position.get("id"), + price_low=Balance.from_tao(tick_to_price(position.get("tick_low"))), + price_high=Balance.from_tao( + tick_to_price(position.get("tick_high")) + ), + liquidity=Balance.from_rao(position.get("liquidity")), + fees_tao=fees_tao, + fees_alpha=fees_alpha, + netuid=position.get("netuid"), ) ) @@ -2543,7 +2541,7 @@ def get_mechanism_emission_split( params=[netuid], block_hash=block_hash, ) - if result is None: + if result.value is None: return None total = sum(result.value) return [round(i / total * 100) for i in result.value] @@ -2578,7 +2576,7 @@ 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 query.value or 1 def get_metagraph_info( self, @@ -2928,7 +2926,7 @@ def get_owned_hotkeys( params=[coldkey_ss58], block_hash=block_hash, ) - return owned_hotkeys + return owned_hotkeys.value def get_parents( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None @@ -3356,8 +3354,7 @@ def get_root_claimable_all_rates( params=[hotkey_ss58], block_hash=self.determine_block_hash(block), ) - query_value = getattr(query, "value", query) - bits_list = next(iter(cast(list[list[tuple[int, FixedPoint]]], query_value))) + bits_list = next(iter(cast(list[list[tuple[int, FixedPoint]]], query.value))) return {bits[0]: fixed_to_float(bits[1], frac_bits=32) for bits in bits_list} def get_root_claimable_stake( @@ -3667,7 +3664,7 @@ def get_staking_hotkeys( params=[coldkey_ss58], block_hash=self.determine_block_hash(block), ) - return result or [] + return result.value or [] def get_start_call_delay(self, block: Optional[int] = None) -> int: """ @@ -3953,7 +3950,6 @@ def get_timestamp(self, block: Optional[int] = None) -> datetime: datetime object for the timestamp of the block """ unix = self.query_module("Timestamp", "Now", block=block) - assert unix is not None return datetime.fromtimestamp(unix.value / 1000, tz=timezone.utc) def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]: @@ -4857,7 +4853,7 @@ def validate_extrinsic_params( ) # Expected params from metadata - expected_params = func_meta.get_param_info() + expected_params = func_meta.get_param_info() # type: ignore provided_params = {} # Validate and filter parameters @@ -5004,6 +5000,7 @@ def sign_and_send_extrinsic( extrinsic_response.extrinsic_receipt = response if response.is_success: + assert response.total_fee_amount is not None extrinsic_response.extrinsic_fee = Balance.from_rao( response.total_fee_amount ) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index a30cfe3b17..8ae791f9f3 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -5,6 +5,7 @@ import numpy as np from numpy.typing import NDArray +from scalecodec.utils.math import FixedPoint from bittensor.core import settings from bittensor.core.chain_data import NeuronInfo, NeuronInfoLite @@ -575,3 +576,14 @@ class BlockInfo: header: dict extrinsics: list explorer: str + + +# TypedDicts +class PositionResponse(TypedDict): + id: int + netuid: int + tick_low: int + tick_high: int + liquidity: int + fees_tao: FixedPoint + fees_alpha: FixedPoint diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 6690f17fb3..393481b42f 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,6 +1,9 @@ -from typing import Optional, TypedDict, Union +from typing import Optional, Union -from scalecodec.utils.math import fixed_to_float as fixed_to_float +from scalecodec.utils.math import ( + fixed_to_float as fixed_to_float, + FixedPoint as FixedPoint, +) from bittensor.core import settings from bittensor.core.errors import BalanceTypeError, BalanceUnitMismatchError @@ -362,17 +365,6 @@ def set_unit(self, netuid: int): return self -class FixedPoint(TypedDict): - """ - Represents a fixed point ``U64F64`` number. - Where ``bits`` is a U128 representation of the fixed point number. - - This matches the type of the Alpha shares. - """ - - bits: int - - # lowercase is added for backwards compatibility to not break API units = UNITS = [ chr( diff --git a/bittensor/utils/liquidity.py b/bittensor/utils/liquidity.py index 16b778449e..3cc253c01d 100644 --- a/bittensor/utils/liquidity.py +++ b/bittensor/utils/liquidity.py @@ -5,9 +5,9 @@ """ import math -from typing import Any from dataclasses import dataclass +from bittensor.core.types import PositionResponse from bittensor.utils import ChainFeatureDisabledWarning, deprecated_message from bittensor.utils.balance import Balance, fixed_to_float @@ -155,7 +155,7 @@ def get_fees_in_range( def calculate_fees( - position: dict[str, Any], + position: PositionResponse, global_fees_tao: float, global_fees_alpha: float, tao_fees_below_low: float, diff --git a/pyproject.toml b/pyproject.toml index 7ddaf7e5ee..2e25751e25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "retry==0.9.2", "requests>=2.0.0,<3.0", "pydantic>=2.3,<3", - "cyscale==0.2.0", + "cyscale==0.2.1", "uvicorn", "bittensor-drand>=1.3.0,<2.0.0", "bittensor-wallet==4.0.1", From 32247d55e4b8111ff54c100aa27f14deefff50d4 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Tue, 14 Apr 2026 21:43:47 +0200 Subject: [PATCH 11/38] Checkin --- bittensor/core/async_subtensor.py | 2 +- tests/unit_tests/test_async_subtensor.py | 84 +++++++++--------------- tests/unit_tests/test_subtensor.py | 12 ++-- 3 files changed, 37 insertions(+), 61 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d9e4ef51e2..4b4cbb78f3 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -504,7 +504,7 @@ async def _runtime_method_exists( runtime = await self.substrate.init_runtime(block_hash=block_hash) if runtime.metadata_v15 is not None: try: - _ = runtime.runtime_api_map[method] + _ = runtime.runtime_api_map[api][method] return True except KeyError: return False diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 8a24fd0d45..dda5957a91 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2906,21 +2906,15 @@ async def test_get_metagraph_info_all_fields(subtensor, mocker): async_subtensor.MetagraphInfo, "from_dict", return_value="parsed_metagraph" ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - {"name": "get_selective_mechagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + "get_selective_mechagraph": {"name": "get_selective_mechagraph"}, + }, } mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", @@ -2966,21 +2960,15 @@ async def test_get_metagraph_info_specific_fields(subtensor, mocker): async_subtensor.MetagraphInfo, "from_dict", return_value="parsed_metagraph" ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - {"name": "get_selective_mechagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + "get_selective_mechagraph": {"name": "get_selective_mechagraph"}, + }, } mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", @@ -3019,21 +3007,15 @@ async def test_get_metagraph_info_subnet_not_exist(subtensor, mocker): return_value=None, ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - {"name": "get_selective_mechagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + "get_selective_mechagraph": {"name": "get_selective_mechagraph"}, + }, } mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", @@ -3078,25 +3060,19 @@ async def test_get_metagraph_info_older_runtime_version( "runtime_call", ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + }, } if block == 6_800_000: # only the newer block should have 'mechagraph' runtime - mocked_runtime_metadata_v15["apis"][0]["methods"].append( - {"name": "get_selective_mechagraph"} - ) + mocked_runtime_metadata_v15["SubnetInfoRuntimeApi"][ + "get_selective_mechagraph" + ] = {"name": "get_selective_mechagraph"} mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 3d34d2980f..3962c5d63c 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2958,9 +2958,7 @@ def test_get_owned_hotkeys_happy_path(subtensor, mocker): # Prep fake_coldkey = "fake_hotkey" fake_hotkey = "fake_hotkey" - fake_hotkeys = [ - fake_hotkey, - ] + fake_hotkeys = mocker.MagicMock(spec=ScaleType, value=[fake_hotkey]) mocked_subtensor = mocker.Mock(return_value=fake_hotkeys) mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor) @@ -2974,15 +2972,17 @@ def test_get_owned_hotkeys_happy_path(subtensor, mocker): params=[fake_coldkey], block_hash=None, ) - assert result == fake_hotkeys + assert result == fake_hotkeys.value def test_get_owned_hotkeys_return_empty(subtensor, mocker): """Tests that the output of get_owned_hotkeys is empty.""" # Prep fake_coldkey = "fake_hotkey" - mocked_subtensor = mocker.Mock(return_value=[]) - mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor) + mocked_return = mocker.MagicMock(spec=ScaleType, value=[]) + mocked_subtensor = mocker.patch.object( + subtensor.substrate, "query", return_value=mocked_return + ) # Call result = subtensor.get_owned_hotkeys(fake_coldkey) From 6421b67b083c8193931ac755717686bf51f1af9f Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 12:39:49 +0200 Subject: [PATCH 12/38] Unit tests passed --- bittensor/core/subtensor.py | 2 +- tests/unit_tests/test_subtensor.py | 112 +++++++++++------------------ 2 files changed, 43 insertions(+), 71 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 28c97d3c31..c5a4f8c6af 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -396,7 +396,7 @@ def _runtime_method_exists(self, api: str, method: str, block_hash: str) -> bool runtime = self.substrate.init_runtime(block_hash=block_hash) if runtime.metadata_v15 is not None: try: - _ = runtime.runtime_api_map[method] + _ = runtime.runtime_api_map[api][method] return True except KeyError: return False diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 3962c5d63c..44b178597f 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3113,21 +3113,15 @@ def test_get_metagraph_info_all_fields(subtensor, mocker): subtensor_module.MetagraphInfo, "from_dict", return_value="parsed_metagraph" ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - {"name": "get_selective_mechagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + "get_selective_mechagraph": {"name": "get_selective_mechagraph"}, + }, } mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", @@ -3169,21 +3163,15 @@ def test_get_metagraph_info_specific_fields(subtensor, mocker): return_value="0xfakechainhead", ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - {"name": "get_selective_mechagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + "get_selective_mechagraph": {"name": "get_selective_mechagraph"}, + }, } mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", @@ -3224,21 +3212,15 @@ def test_get_metagraph_info_subnet_not_exist(subtensor, mocker): return_value=None, ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - {"name": "get_selective_mechagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + "get_selective_mechagraph": {"name": "get_selective_mechagraph"}, + }, } mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", @@ -3282,25 +3264,19 @@ def test_get_metagraph_info_older_runtime_version( "runtime_call", ) mocked_runtime_metadata_v15 = { - "apis": [ - { - "name": "SubnetInfoRuntimeApi", - "methods": [ - {"name": "get_selective_metagraph"}, - {"name": "get_metagraph"}, - ], - }, - ] + "SubnetInfoRuntimeApi": { + "get_selective_metagraph": {"name": "get_selective_metagraph"}, + "get_metagraph": {"name": "get_metagraph"}, + }, } if block == 6_800_000: # only the newer block should have 'mechagraph' runtime - mocked_runtime_metadata_v15["apis"][0]["methods"].append( - {"name": "get_selective_mechagraph"} - ) + mocked_runtime_metadata_v15["SubnetInfoRuntimeApi"][ + "get_selective_mechagraph" + ] = {"name": "get_selective_mechagraph"} mocked_runtime = mocker.Mock(spec=Runtime) - mocked_metadata = mocker.Mock() - mocked_metadata.value.return_value = mocked_runtime_metadata_v15 - mocked_runtime.metadata_v15 = mocked_metadata + mocked_runtime.metadata_v15 = mocker.Mock() + mocked_runtime.runtime_api_map = mocked_runtime_metadata_v15 mocker.patch.object( subtensor.substrate, "init_runtime", @@ -3724,18 +3700,16 @@ def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): # Fake positions to return from query_map fake_positions = [ [ - (2,), - mocker.Mock( - value={ - "id": (2,), - "netuid": 2, - "tick_low": (206189,), - "tick_high": (208196,), - "liquidity": 1000000000000, - "fees_tao": {"bits": 0}, - "fees_alpha": {"bits": 0}, - } - ), + 2, + { + "id": 2, + "netuid": 2, + "tick_low": 206189, + "tick_high": 208196, + "liquidity": 1000000000000, + "fees_tao": {"bits": 0}, + "fees_alpha": {"bits": 0}, + }, ], ] fake_result = mocker.MagicMock(records=fake_positions, autospec=list) @@ -4197,7 +4171,7 @@ def test_get_timelocked_weight_commits(subtensor, mocker): @pytest.mark.parametrize( "query_return, expected_result", ( - ["value", [10, 90]], + [[6553, 58982], [10, 90]], [None, None], ), ) @@ -4205,9 +4179,7 @@ def test_get_mechanism_emission_split(subtensor, mocker, query_return, expected_ """Verify that get_mechanism_emission_split calls the correct methods.""" # Preps netuid = mocker.Mock() - query_return = ( - mocker.Mock(value=[6553, 58982]) if query_return == "value" else query_return - ) + query_return = mocker.Mock(value=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 From e2b79f4e77050434bdc723e162cb7668b3d9c8ce Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 14:49:35 +0200 Subject: [PATCH 13/38] Progress --- bittensor/core/async_subtensor.py | 106 ++++++++++------------ bittensor/core/chain_data/coldkey_swap.py | 9 +- bittensor/core/subtensor.py | 89 +++++++++--------- bittensor/core/types.py | 5 + bittensor/utils/__init__.py | 10 +- tests/e2e_tests/test_axon.py | 1 + 6 files changed, 107 insertions(+), 113 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4b4cbb78f3..d9fe2e9e12 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -147,6 +147,8 @@ SubtensorMixin, UIDs, Weights, + PositionResponse, + NeuronCertificateResponse, ) from bittensor.utils import ( Certificate, @@ -1678,13 +1680,13 @@ async def get_balance( Balance: The balance object containing the account's TAO balance. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - balance = await self.substrate.query( + balance: ScaleType[dict[str, Any]] = await self.substrate.query( module="System", storage_function="Account", params=[address], block_hash=block_hash, ) - return Balance(balance["data"]["free"]) + return Balance(balance.value["data"]["free"]) async def get_balances( self, @@ -1934,7 +1936,7 @@ async def get_coldkey_swap_announcement( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional["ColdkeySwapAnnouncementInfo"]: + ) -> Optional[ColdkeySwapAnnouncementInfo]: """ Retrieves coldkey swap announcement for a specific coldkey. @@ -1957,13 +1959,13 @@ async def get_coldkey_swap_announcement( - See: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[Optional[tuple[int, str]]] = await self.substrate.query( module="SubtensorModule", storage_function="ColdkeySwapAnnouncements", params=[coldkey_ss58], block_hash=block_hash, ) - if query is None: + if query.value is None: return None return ColdkeySwapAnnouncementInfo.from_query( coldkey_ss58=coldkey_ss58, query=query @@ -2969,11 +2971,12 @@ async def get_liquidity_list( current_tick = price_to_tick(sqrt_price**2) # Fetch positions - positions_values: list[tuple[dict, int, int]] = [] + positions_values: list[tuple[PositionResponse, int, int]] = [] positions_storage_keys: list[StorageKey] = [] + position: PositionResponse async for _, position in positions_response: - tick_low_idx = position.get("tick_low")[0] - tick_high_idx = position.get("tick_high")[0] + tick_low_idx = position.get("tick_low") + tick_high_idx = position.get("tick_high") positions_values.append((position, tick_low_idx, tick_high_idx)) tick_low_sk = await self.substrate.create_storage_key( pallet="Swap", @@ -3052,12 +3055,12 @@ async def get_liquidity_list( positions.append( LiquidityPosition( **{ - "id": position.get("id")[0], + "id": position.get("id"), "price_low": Balance.from_tao( - tick_to_price(position.get("tick_low")[0]) + tick_to_price(position.get("tick_low")) ), "price_high": Balance.from_tao( - tick_to_price(position.get("tick_high")[0]) + tick_to_price(position.get("tick_high")) ), "liquidity": Balance.from_rao(position.get("liquidity")), "fees_tao": fees_tao, @@ -3409,22 +3412,21 @@ async def get_neuron_certificate( This function is used for certificate discovery for setting up mutual tls communication between neurons. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - certificate = cast( - Union[str, dict], - await self.query_module( - module="SubtensorModule", - name="NeuronCertificates", - block_hash=block_hash, - reuse_block=reuse_block, - params=[netuid, hotkey_ss58], - ), + certificate_query: ScaleType[ + Optional[str | NeuronCertificateResponse] + ] = await self.query_module( + module="SubtensorModule", + name="NeuronCertificates", + block_hash=block_hash, + reuse_block=reuse_block, + params=[netuid, hotkey_ss58], ) - try: - if certificate: + certificate: Optional[NeuronCertificateResponse] = certificate_query.value + if certificate is not None: + try: return Certificate(certificate) - - except AttributeError: - return None + except AttributeError: + return None return None async def get_neuron_for_pubkey_and_subnet( @@ -4460,17 +4462,14 @@ async def get_start_call_delay( Return: Amount of blocks after the start call can be executed. """ - return cast( - int, - ( - await self.query_subtensor( - name="StartCallDelay", - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ) - ), - ) + return ( + await self.query_subtensor( + name="StartCallDelay", + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + ).value async def get_subnet_burn_cost( self, @@ -4957,20 +4956,17 @@ async def get_vote_data( network, particularly how proposals are received and acted upon by the governing body. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - vote_data = cast( - Optional[dict[str, Any]], - await self.substrate.query( - module="Triumvirate", - storage_function="Voting", - params=[proposal_hash], - block_hash=block_hash, - ), + vote_data: ScaleType[Optional[dict[str, Any]]] = await self.substrate.query( + module="Triumvirate", + storage_function="Voting", + params=[proposal_hash], + block_hash=block_hash, ) - if vote_data is None: + if vote_data.value is None: return None - return ProposalVoteData.from_dict(vote_data) + return ProposalVoteData.from_dict(vote_data.value) async def get_uid_for_hotkey_on_subnet( self, @@ -5328,7 +5324,11 @@ async def is_subnet_active( reuse_block=reuse_block, params=[netuid], ) - return True if query and query.value > 0 else False + qv: Optional[int] = query.value + if qv is None or qv <= 0: + return False + else: + return True async def last_drand_round(self) -> Optional[int]: """Retrieves the last drand round emitted in Bittensor. @@ -5608,16 +5608,10 @@ async def query_identity( block_hash=block_hash, ) - if not identity_info: - return None - - try: - identity_data = getattr(identity_info, "value", identity_info) - return ChainIdentity.from_dict( - decode_hex_identity_dict(cast(dict[str, Any], identity_data)), - ) - except TypeError: + identity_data: Optional[dict[str, Any]] = identity_info.value + if identity_info is None: return None + return ChainIdentity.from_dict(decode_hex_identity_dict(identity_data)) async def recycle( self, diff --git a/bittensor/core/chain_data/coldkey_swap.py b/bittensor/core/chain_data/coldkey_swap.py index 6388ce452b..7407a3abb9 100644 --- a/bittensor/core/chain_data/coldkey_swap.py +++ b/bittensor/core/chain_data/coldkey_swap.py @@ -31,7 +31,7 @@ class ColdkeySwapAnnouncementInfo: @classmethod def from_query( - cls, coldkey_ss58: str, query: Optional[ScaleType] + cls, coldkey_ss58: str, query: ScaleType ) -> Optional["ColdkeySwapAnnouncementInfo"]: """ Creates a ColdkeySwapAnnouncementInfo object from a Substrate query result. @@ -43,11 +43,10 @@ def from_query( Returns: ColdkeySwapAnnouncementInfo if announcement exists, None otherwise. """ - if not getattr(query, "value", None): + if query.value is None: return None - - execution_block = query.value[0] - new_coldkey_hash = "0x" + bytes(query.value[1][0]).hex() + qv: tuple[int, str] = query.value + execution_block, new_coldkey_hash = qv return cls( coldkey=coldkey_ss58, execution_block=execution_block, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index c5a4f8c6af..1682275612 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -147,6 +147,7 @@ UIDs, Weights, PositionResponse, + NeuronCertificateResponse, ) from bittensor.utils import ( Certificate, @@ -520,8 +521,7 @@ def get_hyperparameter( params=[netuid], block_hash=block_hash, ) - - return getattr(result, "value", result) + return result.value @property def block(self) -> int: @@ -1340,13 +1340,13 @@ def get_balance(self, address: str, block: Optional[int] = None) -> Balance: Returns: Balance: The balance object containing the account's TAO balance. """ - balance = self.substrate.query( + balance: ScaleType[dict[str, Any]] = self.substrate.query( module="System", storage_function="Account", params=[address], block_hash=self.determine_block_hash(block), ) - return Balance(cast(dict[str, Any], balance)["data"]["free"]) + return Balance(balance.value["data"]["free"]) def get_balances( self, @@ -1591,16 +1591,16 @@ def get_coldkey_swap_announcement( - See: """ block_hash = self.determine_block_hash(block) - query = self.substrate.query( + query: ScaleType[Optional[tuple[int, str]]] = self.substrate.query( module="SubtensorModule", storage_function="ColdkeySwapAnnouncements", params=[coldkey_ss58], block_hash=block_hash, ) - if query is None: + if query.value is None: return None return ColdkeySwapAnnouncementInfo.from_query( - coldkey_ss58=coldkey_ss58, query=cast(ScaleType, query) + coldkey_ss58=coldkey_ss58, query=query ) def get_coldkey_swap_announcements( @@ -1727,12 +1727,12 @@ def get_coldkey_swap_reannouncement_delay( - See: """ block_hash = self.determine_block_hash(block) - query = self.substrate.query( + query: ScaleType[Optional[int]] = self.substrate.query( module="SubtensorModule", storage_function="ColdkeySwapReannouncementDelay", block_hash=block_hash, ) - return cast(int, query.value) or 0 + return query.value or 0 def get_coldkey_swap_dispute( self, @@ -2124,13 +2124,13 @@ def get_delegate_take(self, hotkey_ss58: str, block: Optional[int] = None) -> fl Notes: - """ - result = self.query_subtensor( + result: ScaleType[int] = self.query_subtensor( name="Delegates", block=block, params=[hotkey_ss58], ) - return u16_normalized_float(result.value) # type: ignore + return u16_normalized_float(result.value) def get_delegated( self, coldkey_ss58: str, block: Optional[int] = None @@ -2821,18 +2821,20 @@ def get_neuron_certificate( This function is used for certificate discovery for setting up mutual tls communication between neurons. """ - certificate_query = self.query_module( - module="SubtensorModule", - name="NeuronCertificates", - block=block, - params=[netuid, hotkey_ss58], + certificate_query: ScaleType[Optional[str | NeuronCertificateResponse]] = ( + self.query_module( + module="SubtensorModule", + name="NeuronCertificates", + block=block, + params=[netuid, hotkey_ss58], + ) ) - try: - if certificate_query: - certificate = cast(dict, certificate_query) + certificate: Optional[NeuronCertificateResponse] = certificate_query.value + if certificate is not None: + try: return Certificate(certificate) - except AttributeError: - return None + except AttributeError: + return None return None def get_neuron_for_pubkey_and_subnet( @@ -3676,13 +3678,10 @@ def get_start_call_delay(self, block: Optional[int] = None) -> int: Return: Amount of blocks after the start call can be executed. """ - return cast( - int, - self.query_subtensor( - name="StartCallDelay", - block=block, - ), - ) + return self.query_subtensor( + name="StartCallDelay", + block=block, + ).value def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[Balance]: """ @@ -4064,20 +4063,17 @@ def get_vote_data( This function is important for tracking and understanding the decision-making processes within the Bittensor network, particularly how proposals are received and acted upon by the governing body. """ - vote_data = cast( - Optional[dict[str, Any]], - self.substrate.query( - module="Triumvirate", - storage_function="Voting", - params=[proposal_hash], - block_hash=self.determine_block_hash(block), - ), + vote_data: ScaleType[Optional[dict[str, Any]]] = self.substrate.query( + module="Triumvirate", + storage_function="Voting", + params=[proposal_hash], + block_hash=self.determine_block_hash(block), ) if vote_data is None: return None - return ProposalVoteData.from_dict(vote_data) + return ProposalVoteData.from_dict(vote_data.value) def get_uid_for_hotkey_on_subnet( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None @@ -4348,7 +4344,11 @@ def is_subnet_active(self, netuid: int, block: Optional[int] = None) -> bool: block=block, params=[netuid], ) - return True if query and query.value > 0 else False + qv: Optional[int] = query.value + if qv is None or qv <= 0: + return False + else: + return True def last_drand_round(self) -> Optional[int]: """Retrieves the last drand round emitted in Bittensor. @@ -4574,17 +4574,10 @@ def query_identity( params=[coldkey_ss58], block_hash=self.determine_block_hash(block), ) - - if not identity_info: - return None - - try: - identity_data = identity_info.value - return ChainIdentity.from_dict( - decode_hex_identity_dict(cast(dict[str, Any], identity_data)), - ) - except TypeError: + identity_data: Optional[dict[str, dict[Any, Any] | str]] = identity_info.value + if identity_data is None: return None + return ChainIdentity.from_dict(decode_hex_identity_dict(identity_data)) def recycle(self, netuid: int, block: Optional[int] = None) -> Optional[Balance]: """Retrieves the 'Burn' hyperparameter for a specified subnet. diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 8ae791f9f3..975f120ee2 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -587,3 +587,8 @@ class PositionResponse(TypedDict): liquidity: int fees_tao: FixedPoint fees_alpha: FixedPoint + + +class NeuronCertificateResponse(TypedDict): + public_key: str + algorithm: int diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 54a11316a3..1134fb4025 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -14,7 +14,7 @@ from bittensor_wallet import Keypair from bittensor_wallet.errors import KeyFileError, PasswordError from bittensor_wallet.utils import SS58_FORMAT -from scalecodec import ( +from scalecodec.utils.ss58 import ( ss58_decode, ss58_encode, is_valid_ss58_address as _is_valid_ss58_address, @@ -22,6 +22,7 @@ from bittensor.core import settings from bittensor.utils.btlogging import logging +from bittensor.core.types import NeuronCertificateResponse from .registration import torch, use_torch from .version import check_version, VersionCheckError @@ -93,10 +94,11 @@ def get_netuid_and_mechid_by_storage_index(storage_index: int) -> tuple[int, int class Certificate(str): - def __new__(cls, data: Union[str, dict]): + def __new__(cls, data: str | NeuronCertificateResponse): if isinstance(data, dict): - tuple_ascii = data["public_key"][0] - string = chr(data["algorithm"]) + "".join(chr(i) for i in tuple_ascii) + pubkey: str = data["public_key"] + pubkey_bytes = bytes.fromhex(pubkey.removeprefix("0x")) + string = chr(data["algorithm"]) + "".join([chr(i) for i in pubkey_bytes]) else: string = data return str.__new__(cls, string) diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index e1574383cc..bea707675c 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -82,6 +82,7 @@ def test_axon(subtensor, templates, alice_wallet): ) +@pytest.mark.skip @pytest.mark.asyncio async def test_axon_async(async_subtensor, templates, alice_wallet): """ From 12f974579e8dcee1a2c6155410b63175e8ab9d3a Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 15:24:57 +0200 Subject: [PATCH 14/38] Checkin --- bittensor/core/async_subtensor.py | 43 +++++++++++++++---------------- bittensor/core/subtensor.py | 35 ++++++++++++------------- bittensor/utils/__init__.py | 5 ++-- tests/e2e_tests/test_axon.py | 1 - 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d9fe2e9e12..ba20800755 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3095,15 +3095,15 @@ async def get_mechanism_emission_split( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self._query_with_fallback( + result: ScaleType[Optional[list[int]]] = await self._query_with_fallback( ("SubtensorModule", "MechanismEmissionSplit", [netuid]), block_hash=block_hash, default_value=None, ) - if result is None or not hasattr(result, "value"): + if result.value is None: return None - - return [round(i / sum(result.value) * 100) for i in result.value] + total = sum(result.value) + return [round(i / total * 100) for i in result.value] async def get_mechanism_count( self, @@ -3127,12 +3127,12 @@ async def get_mechanism_count( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self._query_with_fallback( + query: ScaleType[Optional[int]] = await self._query_with_fallback( ("SubtensorModule", "MechanismCountCurrent", [netuid]), block_hash=block_hash, default_value=None, ) - return getattr(query, "value", 1) + return query.value or 1 async def get_metagraph_info( self, @@ -3228,13 +3228,13 @@ async def get_metagraph_info( default_value=None, ) - if getattr(query, "value", None) is None: + if query is None: logging.error( f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) return None - return MetagraphInfo.from_dict(query.value) + return MetagraphInfo.from_dict(query) async def get_mev_shield_current_key( self, @@ -3261,16 +3261,16 @@ async def get_mev_shield_current_key( announced a key yet. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[Optional[bytearray]] = await self.substrate.query( module="MevShield", storage_function="CurrentKey", block_hash=block_hash, ) - if query is None: + if query.value is None: return None - public_key_bytes = bytes(next(iter(query))) + public_key_bytes = bytes(query.value_object) # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: @@ -3306,16 +3306,16 @@ async def get_mev_shield_next_key( announced the next key yet. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[Optional[bytearray]] = await self.substrate.query( module="MevShield", storage_function="NextKey", block_hash=block_hash, ) - if query is None: + if query.value is None: return None - public_key_bytes = bytes(next(iter(query))) + public_key_bytes = bytes(query.value_object) # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: @@ -3347,7 +3347,7 @@ async def get_minimum_required_stake(self): module="SubtensorModule", storage_function="NominatorMinRequiredStake" ) - return Balance.from_rao(getattr(result, "value", 0)) + return Balance.from_rao(result.value or 0) async def get_netuids_for_hotkey( self, @@ -3436,7 +3436,7 @@ async def get_neuron_for_pubkey_and_subnet( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> "NeuronInfo": + ) -> NeuronInfo: """ Retrieves information about a neuron based on its public key (hotkey SS58 address) and the specific subnet UID (netuid). This function provides detailed neuron information for a particular subnet within the Bittensor @@ -3462,7 +3462,7 @@ async def get_neuron_for_pubkey_and_subnet( params=[netuid, hotkey_ss58], block_hash=block_hash, ) - if (uid := getattr(uid_query, "value", None)) is None: + if (uid := uid_query.value) is None: return NeuronInfo.get_null_neuron() else: return await self.neuron_for_uid( @@ -3540,7 +3540,7 @@ async def get_owned_hotkeys( block_hash=block_hash, ) - return owned_hotkeys or [] + return owned_hotkeys.value or [] async def get_parents( self, @@ -3575,7 +3575,7 @@ async def get_parents( params=[hotkey_ss58, netuid], block_hash=block_hash, ) - if parents: + if parents.value: formatted_parents = [] for proportion, formatted_child in parents.value: # Convert U64 to int @@ -3692,14 +3692,13 @@ async def get_proxy_announcement( - See: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[tuple[list[dict], int]] = await self.substrate.query( module="Proxy", storage_function="Announcements", params=[delegate_account_ss58], block_hash=block_hash, ) - query_value = getattr(query, "value", query) - return ProxyAnnouncementInfo.from_dict(cast(list[Any], query_value)[0]) + return ProxyAnnouncementInfo.from_dict(query.value) async def get_proxy_announcements( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 1682275612..95f9e20487 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2535,7 +2535,7 @@ def get_mechanism_emission_split( module, storage_function, block_hash=block_hash ): return None - result = self.substrate.query( + result: ScaleType[Optional[list[int]]] = self.substrate.query( module="SubtensorModule", storage_function="MechanismEmissionSplit", params=[netuid], @@ -2570,7 +2570,7 @@ def get_mechanism_count( module, storage_function, block_hash=block_hash ): return 1 - query = self.substrate.query( + query: ScaleType[Optional[int]] = self.substrate.query( module=module, storage_function=storage_function, params=[netuid], @@ -2665,13 +2665,13 @@ def get_metagraph_info( default_value=None, ) - if query is None or not hasattr(query, "value") or query.value is None: + if query is None: logging.error( f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." ) return None - return MetagraphInfo.from_dict(query.value) + return MetagraphInfo.from_dict(query) def get_mev_shield_current_key( self, block: Optional[int] = None @@ -2693,16 +2693,16 @@ def get_mev_shield_current_key( announced a key yet. """ block_hash = self.determine_block_hash(block=block) - query = self.substrate.query( + query: ScaleType[Optional[bytearray]] = self.substrate.query( module="MevShield", storage_function="CurrentKey", block_hash=block_hash, ) - if query is None: + if query.value is None: return None - public_key_bytes = bytes(next(iter(query))) + public_key_bytes = bytes(query.value_object) # Validate public_key size for ML-KEM-768 if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: @@ -2731,16 +2731,16 @@ def get_mev_shield_next_key(self, block: Optional[int] = None) -> Optional[bytes announced the next key yet. """ block_hash = self.determine_block_hash(block=block) - query = self.substrate.query( + query: ScaleType[Optional[bytearray]] = self.substrate.query( module="MevShield", storage_function="NextKey", block_hash=block_hash, ) - if query is None: + if query.value is None: return None - public_key_bytes = bytes(next(iter(query))) + public_key_bytes = bytes(query.value_object) # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: @@ -2772,7 +2772,7 @@ def get_minimum_required_stake(self) -> Balance: module="SubtensorModule", storage_function="NominatorMinRequiredStake" ) - return Balance.from_rao(getattr(result, "value", 0)) + return Balance.from_rao(result.value or 0) def get_netuids_for_hotkey( self, hotkey_ss58: str, block: Optional[int] = None @@ -2839,7 +2839,7 @@ def get_neuron_certificate( def get_neuron_for_pubkey_and_subnet( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None - ) -> Optional["NeuronInfo"]: + ) -> NeuronInfo: """ Retrieves information about a neuron based on its public key (hotkey SS58 address) and the specific subnet UID (netuid). This function provides detailed neuron information for a particular subnet within the Bittensor @@ -2863,7 +2863,7 @@ def get_neuron_for_pubkey_and_subnet( params=[netuid, hotkey_ss58], block_hash=block_hash, ) - if (uid := getattr(uid_query, "value", None)) is None: + if (uid := uid_query.value) is None: return NeuronInfo.get_null_neuron() return self.neuron_for_uid( @@ -2928,7 +2928,7 @@ def get_owned_hotkeys( params=[coldkey_ss58], block_hash=block_hash, ) - return owned_hotkeys.value + return owned_hotkeys.value or [] def get_parents( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None @@ -2955,7 +2955,7 @@ def get_parents( params=[hotkey_ss58, netuid], block_hash=self.determine_block_hash(block), ) - if parents: + if parents.value: formatted_parents = [] for proportion, formatted_child in parents.value: # Convert U64 to int @@ -3057,14 +3057,13 @@ def get_proxy_announcement( - See: """ block_hash = self.determine_block_hash(block) - query = self.substrate.query( + query: ScaleType[tuple[list[dict], int]] = self.substrate.query( module="Proxy", storage_function="Announcements", params=[delegate_account_ss58], block_hash=block_hash, ) - query_value = getattr(query, "value", query) - return ProxyAnnouncementInfo.from_dict(cast(list[Any], query_value)[0]) + return ProxyAnnouncementInfo.from_dict(query.value) def get_proxy_announcements( self, diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 1134fb4025..6a2197132d 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -22,13 +22,12 @@ from bittensor.core import settings from bittensor.utils.btlogging import logging -from bittensor.core.types import NeuronCertificateResponse from .registration import torch, use_torch from .version import check_version, VersionCheckError if TYPE_CHECKING: from bittensor_wallet import Wallet - from bittensor.core.types import ExtrinsicResponse + from bittensor.core.types import ExtrinsicResponse, NeuronCertificateResponse # keep save from import analyzer as obvious aliases hex_to_ss58 = ss58_encode @@ -94,7 +93,7 @@ def get_netuid_and_mechid_by_storage_index(storage_index: int) -> tuple[int, int class Certificate(str): - def __new__(cls, data: str | NeuronCertificateResponse): + def __new__(cls, data: "str | NeuronCertificateResponse"): if isinstance(data, dict): pubkey: str = data["public_key"] pubkey_bytes = bytes.fromhex(pubkey.removeprefix("0x")) diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index bea707675c..e1574383cc 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -82,7 +82,6 @@ def test_axon(subtensor, templates, alice_wallet): ) -@pytest.mark.skip @pytest.mark.asyncio async def test_axon_async(async_subtensor, templates, alice_wallet): """ From 91d414de1a79349052b9c541cf597b0b1f253840 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 15:56:48 +0200 Subject: [PATCH 15/38] Checkin --- bittensor/core/async_subtensor.py | 55 ++++++++++--------------------- bittensor/core/subtensor.py | 49 ++++++++------------------- 2 files changed, 32 insertions(+), 72 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ba20800755..991e787cf8 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -853,7 +853,7 @@ async def query_module( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[ScaleType[Any]]: + ) -> ScaleType[Any]: """Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This function is a generic query interface that allows for flexible and diverse data retrieval from various blockchain modules. Use this function for nonstandard queries to storage defined within the Bittensor @@ -3868,7 +3868,7 @@ async def get_revealed_commitment_by_hotkey( block_hash=block_hash, reuse_block=reuse_block, ) - if query is None: + if query.value is None: return None return tuple(decode_revealed_commitment(pair) for pair in query) @@ -3878,7 +3878,7 @@ async def get_root_claim_type( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Union[str, dict]: + ) -> str | dict[str, dict[str, list[int]]]: """Return the configured root claim type for a given coldkey. The root claim type controls how dividends from staking to the Root Subnet (subnet 0) are processed when they @@ -3906,30 +3906,15 @@ async def get_root_claim_type( - See also: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[ + str | dict[str, dict[str, list[int]]] + ] = await self.substrate.query( module="SubtensorModule", storage_function="RootClaimType", params=[coldkey_ss58], block_hash=block_hash, ) - query_value = getattr(query, "value", query) - claim_type = cast(dict[str, Any], query_value) - # Query returns enum as dict: {"Swap": ()} or {"Keep": ()} or {"KeepSubnets": {"subnets": [1, 2, 3]}} - variant_name = next(iter(claim_type.keys())) - variant_value = claim_type[variant_name] - - # For simple variants (Swap, Keep), value is empty tuple, return string - if not variant_value or variant_value == (): - return variant_name - - # For KeepSubnets, value contains the data, return full dict structure - if isinstance(variant_value, dict) and "subnets" in variant_value: - subnets_raw = variant_value["subnets"] - subnets = list(subnets_raw[0]) - - return {variant_name: {"subnets": subnets}} - - return {variant_name: variant_value} + return query.value async def get_root_alpha_dividends_per_subnet( self, @@ -3955,14 +3940,13 @@ async def get_root_alpha_dividends_per_subnet( Balance: The root alpha dividends for this hotkey on this subnet in Rao, with unit set to netuid. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[int] = await self.substrate.query( module="SubtensorModule", storage_function="RootAlphaDividendsPerSubnet", params=[netuid, hotkey_ss58], block_hash=block_hash, ) - value = getattr(query, "value", query) - return Balance.from_rao(cast(int, value)).set_unit(netuid=netuid) + return Balance.from_rao(query.value, netuid=netuid) async def get_root_claimable_rate( self, @@ -4023,15 +4007,15 @@ async def get_root_claimable_all_rates( - See: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[list[tuple[int, FixedPoint]]] = await self.substrate.query( module="SubtensorModule", storage_function="RootClaimable", params=[hotkey_ss58], block_hash=block_hash, ) - query_value = getattr(query, "value", query) - bits_list = next(iter(cast(list[list[tuple[int, FixedPoint]]], query_value))) - return {bits[0]: fixed_to_float(bits[1], frac_bits=32) for bits in bits_list} + return { + netuid: fixed_to_float(bits, frac_bits=32) for (netuid, bits) in query.value + } async def get_root_claimable_stake( self, @@ -4122,14 +4106,13 @@ async def get_root_claimed( - See: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[int] = await self.substrate.query( module="SubtensorModule", storage_function="RootClaimed", params=[netuid, hotkey_ss58, coldkey_ss58], block_hash=block_hash, ) - value = getattr(query, "value", query) - return Balance.from_rao(cast(int, value)).set_unit(netuid=netuid) + return Balance.from_rao(query.value, netuid=netuid) async def get_stake( self, @@ -4290,7 +4273,7 @@ async def get_stake_info_for_coldkey( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list["StakeInfo"]: + ) -> list[StakeInfo]: """ Retrieves the stake information for a given coldkey. @@ -4324,7 +4307,7 @@ async def get_stake_info_for_coldkeys( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, list["StakeInfo"]]: + ) -> dict[str, list[StakeInfo]]: """ Retrieves the stake information for multiple coldkeys. @@ -4378,9 +4361,7 @@ async def get_stake_for_hotkey( block_hash=block_hash, reuse_block=reuse_block, ) - balance = Balance.from_rao(hotkey_alpha_query.value) - balance.set_unit(netuid=netuid) - return balance + return Balance.from_rao(hotkey_alpha_query.value, netuid=netuid) get_hotkey_stake = get_stake_for_hotkey diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 95f9e20487..872a1ccd6a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3215,7 +3215,7 @@ def get_revealed_commitment_by_hotkey( params=[netuid, hotkey_ss58], block=block, ) - if query is None: + if query.value is None: return None return tuple(decode_revealed_commitment(pair) for pair in query) @@ -3223,7 +3223,7 @@ def get_root_claim_type( self, coldkey_ss58: str, block: Optional[int] = None, - ) -> Union[str, dict]: + ) -> str | dict[str, dict[str, list[int]]]: """Return the configured root claim type for a given coldkey. The root claim type controls how dividends from staking to the Root Subnet (subnet 0) are processed when they @@ -3253,24 +3253,7 @@ def get_root_claim_type( params=[coldkey_ss58], block_hash=self.determine_block_hash(block), ) - query_value = getattr(query, "value", query) - claim_type = cast(dict[str, Any], query_value) - # Query returns enum as dict: {"Swap": ()} or {"Keep": ()} or {"KeepSubnets": {"subnets": [1, 2, 3]}} - variant_name = next(iter(claim_type.keys())) - variant_value = claim_type[variant_name] - - # For simple variants (Swap, Keep), value is empty tuple, return string - if not variant_value or variant_value == (): - return variant_name - - # For KeepSubnets, value contains the data, return full dict structure - if isinstance(variant_value, dict) and "subnets" in variant_value: - subnets_raw = variant_value["subnets"] - subnets = list(subnets_raw[0]) - - return {variant_name: {"subnets": subnets}} - - return {variant_name: variant_value} + return query.value def get_root_alpha_dividends_per_subnet( self, @@ -3291,14 +3274,13 @@ def get_root_alpha_dividends_per_subnet( Returns: Balance: The root alpha dividends for this hotkey on this subnet in Rao, with unit set to netuid. """ - query = self.substrate.query( + query: ScaleType[int] = self.substrate.query( module="SubtensorModule", storage_function="RootAlphaDividendsPerSubnet", params=[netuid, hotkey_ss58], block_hash=self.determine_block_hash(block), ) - value = getattr(query, "value", query) - return Balance.from_rao(cast(int, value)).set_unit(netuid=netuid) + return Balance.from_rao(query.value, netuid=netuid) def get_root_claimable_rate( self, @@ -3349,14 +3331,15 @@ def get_root_claimable_all_rates( Notes: - See: """ - query = self.substrate.query( + query: ScaleType[list[tuple[int, FixedPoint]]] = self.substrate.query( module="SubtensorModule", storage_function="RootClaimable", params=[hotkey_ss58], block_hash=self.determine_block_hash(block), ) - bits_list = next(iter(cast(list[list[tuple[int, FixedPoint]]], query.value))) - return {bits[0]: fixed_to_float(bits[1], frac_bits=32) for bits in bits_list} + return { + netuid: fixed_to_float(bits, frac_bits=32) for (netuid, bits) in query.value + } def get_root_claimable_stake( self, @@ -3431,14 +3414,13 @@ def get_root_claimed( Notes: - See: """ - query = self.substrate.query( + query: ScaleType[int] = self.substrate.query( module="SubtensorModule", storage_function="RootClaimed", params=[netuid, hotkey_ss58, coldkey_ss58], block_hash=self.determine_block_hash(block), ) - value = getattr(query, "value", query) - return Balance.from_rao(cast(int, value)).set_unit(netuid=netuid) + return Balance.from_rao(query.value, netuid=netuid) def get_stake( self, @@ -3507,7 +3489,7 @@ def get_stake_for_coldkey_and_hotkey( def get_stake_info_for_coldkey( self, coldkey_ss58: str, block: Optional[int] = None - ) -> list["StakeInfo"]: + ) -> list[StakeInfo]: """ Retrieves the stake information for a given coldkey. @@ -3531,7 +3513,7 @@ def get_stake_info_for_coldkey( def get_stake_info_for_coldkeys( self, coldkey_ss58s: list[str], block: Optional[int] = None - ) -> dict[str, list["StakeInfo"]]: + ) -> dict[str, list[StakeInfo]]: """ Retrieves the stake information for multiple coldkeys. @@ -3568,10 +3550,7 @@ def get_stake_for_hotkey( hotkey_alpha_query = self.query_subtensor( name="TotalHotkeyAlpha", params=[hotkey_ss58, netuid], block=block ) - assert hotkey_alpha_query is not None - balance = Balance.from_rao(hotkey_alpha_query.value) - balance.set_unit(netuid=netuid) - return balance + return Balance.from_rao(hotkey_alpha_query.value, netuid=netuid) get_hotkey_stake = get_stake_for_hotkey From bdaf49cab79411cbb78b08bb7dde03033784cb52 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 16:28:03 +0200 Subject: [PATCH 16/38] Mypy --- bittensor/core/async_subtensor.py | 31 ++++++++++++++++++------------- bittensor/core/subtensor.py | 22 +++++++++++++--------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 991e787cf8..f82c96f4c4 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -13,7 +13,7 @@ from bittensor_wallet.utils import SS58_FORMAT from scalecodec import GenericCall from scalecodec.base import ScaleType -from scalecodec.utils.math import FixedPoint +from scalecodec.utils.math import FixedPoint, fixed_to_decimal from bittensor.core.chain_data import ( ColdkeySwapAnnouncementInfo, @@ -1007,14 +1007,18 @@ async def all_subnets( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - query = await self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_all_dynamic_info", - block_hash=block_hash, - ) - subnet_prices = await self.get_subnet_prices(block_hash=block_hash) + decoded: list[dict[str, Any]] + subnet_prices: dict[int, Balance] - decoded = query.decode() + decoded, subnet_prices = await asyncio.gather( + self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", + block_hash=block_hash, + ), + self.get_subnet_prices(block_hash=block_hash), + return_exceptions=True, + ) if not isinstance(subnet_prices, (SubstrateRequestException, ValueError)): for sn in decoded: @@ -3270,7 +3274,9 @@ async def get_mev_shield_current_key( if query.value is None: return None - public_key_bytes = bytes(query.value_object) + value: bytearray = query.value + + public_key_bytes = bytes(value) # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: @@ -4654,11 +4660,10 @@ async def get_subnet_prices( ) prices = {} - async for id_, current_sqrt_price in current_sqrt_prices: - # TODO investigate if we need to use fixed_to_decimal here instead - current_sqrt_price = fixed_to_float(current_sqrt_price) + async for id_, current_sqrt_price_bits in current_sqrt_prices: + current_sqrt_price = fixed_to_decimal(current_sqrt_price_bits) current_price = current_sqrt_price * current_sqrt_price - current_price_in_tao = Balance.from_rao(int(current_price * 1e9)) + current_price_in_tao = Balance.from_tao(float(current_price)) prices.update({id_: current_price_in_tao}) # SN0 price is always 1 TAO diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 872a1ccd6a..48340e1668 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -11,7 +11,7 @@ from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT from scalecodec.base import ScaleType -from scalecodec.utils.math import FixedPoint +from scalecodec.utils.math import FixedPoint, fixed_to_decimal from bittensor.core.axon import Axon from bittensor.core.chain_data import ( @@ -2702,7 +2702,9 @@ def get_mev_shield_current_key( if query.value is None: return None - public_key_bytes = bytes(query.value_object) + value: bytearray = query.value + + public_key_bytes = bytes(value) # Validate public_key size for ML-KEM-768 if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: @@ -2740,7 +2742,9 @@ def get_mev_shield_next_key(self, block: Optional[int] = None) -> Optional[bytes if query.value is None: return None - public_key_bytes = bytes(query.value_object) + value: bytearray = query.value + + public_key_bytes = bytes(value) # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: @@ -2829,7 +2833,7 @@ def get_neuron_certificate( params=[netuid, hotkey_ss58], ) ) - certificate: Optional[NeuronCertificateResponse] = certificate_query.value + certificate: Optional[str | NeuronCertificateResponse] = certificate_query.value if certificate is not None: try: return Certificate(certificate) @@ -3819,10 +3823,10 @@ def get_subnet_prices( ) prices = {} - for id_, current_sqrt_price in current_sqrt_prices: - current_sqrt_price = fixed_to_float(current_sqrt_price) + for id_, current_sqrt_price_bits in current_sqrt_prices: + current_sqrt_price = fixed_to_decimal(current_sqrt_price_bits) current_price = current_sqrt_price * current_sqrt_price - current_price_in_tao = Balance.from_rao(int(current_price * 1e9)) + current_price_in_tao = Balance.from_tao(float(current_price)) prices.update({id_: current_price_in_tao}) # SN0 price is always 1 TAO @@ -4025,7 +4029,7 @@ def get_unstake_fee( def get_vote_data( self, proposal_hash: str, block: Optional[int] = None - ) -> Optional["ProposalVoteData"]: + ) -> Optional[ProposalVoteData]: # TODO: is this all deprecated? Didn't subtensor senate stuff get removed? """ Retrieves the voting data for a specific proposal on the Bittensor blockchain. This data includes information @@ -4048,7 +4052,7 @@ def get_vote_data( block_hash=self.determine_block_hash(block), ) - if vote_data is None: + if vote_data.value is None: return None return ProposalVoteData.from_dict(vote_data.value) From ca36644d48303041d9fe1dbae7245428b6d40f98 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 17:40:29 +0200 Subject: [PATCH 17/38] Checkin --- bittensor/core/async_subtensor.py | 103 ++++++++++---------- bittensor/core/chain_data/crowdloan_info.py | 7 +- bittensor/core/chain_data/utils.py | 3 +- bittensor/core/subtensor.py | 86 ++++++++-------- bittensor/core/types.py | 24 +++++ 5 files changed, 127 insertions(+), 96 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f82c96f4c4..14fe9382db 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -149,6 +149,8 @@ Weights, PositionResponse, NeuronCertificateResponse, + CommitmentOfResponse, + CrowdloansResponse, ) from bittensor.utils import ( Certificate, @@ -346,9 +348,9 @@ async def __aexit__(self, exc_type, exc_val, exc_tb): async def _decode_crowdloan_entry( self, crowdloan_id: int, - data: dict, + data: CrowdloansResponse, block_hash: Optional[str] = None, - ) -> "CrowdloanInfo": + ) -> CrowdloanInfo: """ Internal helper to parse and decode a single Crowdloan record. @@ -640,8 +642,7 @@ async def get_hyperparameter( params=[netuid], block_hash=block_hash, ) - - return getattr(result, "value", result) + return result.value async def sim_swap( self, @@ -1054,14 +1055,14 @@ async def blocks_since_last_step( Notes: - """ - query = await self.query_subtensor( + query: ScaleType[int] = await self.query_subtensor( name="BlocksSinceLastStep", block=block, block_hash=block_hash, reuse_block=reuse_block, params=[netuid], ) - return cast(Optional[int], getattr(query, "value", query)) + return query.value async def blocks_since_last_update( self, @@ -1085,14 +1086,14 @@ async def blocks_since_last_update( """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) block = block or await self.substrate.get_block_number(block_hash) - call = await self.get_hyperparameter( + call: list[int] = await self.get_hyperparameter( param_name="LastUpdate", netuid=netuid, block=block, block_hash=block_hash, reuse_block=reuse_block, ) - return None if call is None else (block - int(call[uid])) + return None if len(call) == 0 else (block - int(call[uid])) async def blocks_until_next_epoch( self, @@ -1176,8 +1177,10 @@ async def bonds( block_hash=block_hash, ) bond_map = [] + uid: int + bond: list[tuple[int, int]] async for uid, bond in b_map_encoded: - if bond is not None: + if len(bond) != 0: bond_map.append((uid, bond)) return bond_map @@ -1206,13 +1209,14 @@ async def commit_reveal_enabled( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - call = await self.get_hyperparameter( + call: Optional[bool] = await self.get_hyperparameter( param_name="CommitRevealWeightsEnabled", block_hash=block_hash, netuid=netuid, reuse_block=reuse_block, ) - return True if call is True else False + assert call is not None + return call async def difficulty( self, @@ -1220,7 +1224,7 @@ async def difficulty( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[int]: + ) -> int: """Retrieves the 'Difficulty' hyperparameter for a specified subnet in the Bittensor network. This parameter determines the computational challenge required for neurons to participate in consensus and @@ -1245,14 +1249,13 @@ async def difficulty( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - call = await self.get_hyperparameter( + call: Optional[int] = await self.get_hyperparameter( param_name="Difficulty", netuid=netuid, block_hash=block_hash, reuse_block=reuse_block, ) - if call is None: - return None + assert call is not None return int(call) async def does_hotkey_exist( @@ -1284,19 +1287,13 @@ async def does_hotkey_exist( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.query( + result: ScaleType[str] = await self.substrate.query( module="SubtensorModule", storage_function="Owner", params=[hotkey_ss58], block_hash=block_hash, ) - return_val = ( - False - if result is None - # not the default key (0x0) - else result != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" - ) - return return_val + return result.value != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" async def get_admin_freeze_window( self, @@ -1323,19 +1320,19 @@ async def get_admin_freeze_window( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[int] = await self.substrate.query( module="SubtensorModule", storage_function="AdminFreezeWindow", block_hash=block_hash, ) - return cast(int, getattr(query, "value", query)) + return query.value async def get_all_subnets_info( self, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list["SubnetInfo"]: + ) -> list[SubnetInfo]: """Retrieves detailed information about all subnets within the Bittensor network. Parameters: @@ -1410,6 +1407,8 @@ async def get_all_commitments( reuse_block=reuse_block, ) result = {} + id_: str + value: CommitmentOfResponse async for id_, value in query: try: result[id_] = decode_metadata(value) @@ -2242,13 +2241,11 @@ async def get_commitment( ) return "" - metadata = cast( - dict, - await self.get_commitment_metadata( - netuid, hotkey, block, block_hash, reuse_block - ), + metadata = await self.get_commitment_metadata( + netuid, hotkey, block, block_hash, reuse_block ) try: + assert not isinstance(metadata, str) return decode_metadata(metadata) except Exception as error: logging.error(error) @@ -2261,7 +2258,7 @@ async def get_commitment_metadata( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Union[str, dict]: + ) -> str | CommitmentOfResponse: # TODO: how to handle return data? need good example @roman """Fetches raw commitment metadata from specific subnet for given hotkey. @@ -2280,15 +2277,17 @@ async def get_commitment_metadata( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - commit_data = await self.substrate.query( + commit_data: ScaleType[ + Optional[CommitmentOfResponse] + ] = await self.substrate.query( module="Commitments", storage_function="CommitmentOf", params=[netuid, hotkey_ss58], block_hash=block_hash, ) - if commit_data is None: + if commit_data.value is None: return "" - return cast(Union[str, dict], getattr(commit_data, "value", commit_data)) + return commit_data.value async def get_crowdloan_constants( self, @@ -2296,7 +2295,7 @@ async def get_crowdloan_constants( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> "CrowdloanConstants": + ) -> CrowdloanConstants: """Retrieves runtime configuration constants governing crowdloan behavior and limits on the Bittensor blockchain. If a list of constant names is provided, only those constants will be queried. @@ -2350,7 +2349,7 @@ async def get_crowdloan_contributions( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, "Balance"]: + ) -> dict[str, Balance]: """Retrieves all contributions made to a specific crowdloan campaign. Returns a mapping of contributor coldkey addresses to their contribution amounts in Rao. @@ -2380,11 +2379,11 @@ async def get_crowdloan_contributions( ) result = {} - - if query.records: - async for record in query: - if record[1]: - result[record[0]] = Balance.from_rao(record[1]) + contributor: str + amount: int + async for contributor, amount in query: + if amount: + result[contributor] = Balance.from_rao(amount) return result @@ -2394,7 +2393,7 @@ async def get_crowdloan_by_id( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional["CrowdloanInfo"]: + ) -> Optional[CrowdloanInfo]: """Retrieves detailed information about a specific crowdloan campaign. Parameters: @@ -2414,13 +2413,13 @@ async def get_crowdloan_by_id( - Crowdloans Overview: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + query: ScaleType[Optional[CrowdloansResponse]] = await self.substrate.query( module="Crowdloan", storage_function="Crowdloans", params=[crowdloan_id], block_hash=block_hash, ) - if not query: + if not query.value: return None return await self._decode_crowdloan_entry( crowdloan_id=crowdloan_id, data=query.value, block_hash=block_hash @@ -2450,13 +2449,12 @@ async def get_crowdloan_next_id( - Crowdloan Tutorial: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.query( + result: ScaleType[int] = await self.substrate.query( module="Crowdloan", storage_function="NextCrowdloanId", block_hash=block_hash, ) - value = getattr(result, "value", result) - return int(value or 0) + return result.value async def get_crowdloans( self, @@ -3271,10 +3269,10 @@ async def get_mev_shield_current_key( block_hash=block_hash, ) - if query.value is None: + if query.value_object is None: return None - value: bytearray = query.value + value: bytearray = query.value_object public_key_bytes = bytes(value) @@ -3318,10 +3316,11 @@ async def get_mev_shield_next_key( block_hash=block_hash, ) - if query.value is None: + if query.value_object is None: return None - public_key_bytes = bytes(query.value_object) + value: bytearray = query.value_object + public_key_bytes = bytes(value) # Validate public_key size for ML-KEM-768 (must be exactly 1184 bytes) if len(public_key_bytes) != MLKEM768_PUBLIC_KEY_SIZE: diff --git a/bittensor/core/chain_data/crowdloan_info.py b/bittensor/core/chain_data/crowdloan_info.py index db7185b78d..51951287af 100644 --- a/bittensor/core/chain_data/crowdloan_info.py +++ b/bittensor/core/chain_data/crowdloan_info.py @@ -1,8 +1,11 @@ from dataclasses import dataclass -from typing import Optional +from typing import Optional, TYPE_CHECKING from bittensor.utils.balance import Balance +if TYPE_CHECKING: + from bittensor.core.types import CrowdloansResponse + @dataclass class CrowdloanInfo: @@ -42,7 +45,7 @@ class CrowdloanInfo: contributors_count: int @classmethod - def from_dict(cls, idx: int, data: dict) -> "CrowdloanInfo": + def from_dict(cls, idx: int, data: "CrowdloansResponse") -> "CrowdloanInfo": """Returns a CrowdloanInfo object from decoded chain data.""" return cls( id=idx, diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index 2d991319b3..0f142d597e 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -11,6 +11,7 @@ if TYPE_CHECKING: from async_substrate_interface.sync_substrate import QueryMapResult + from bittensor.core.types import CommitmentOfResponse class ChainDataType(Enum): @@ -115,7 +116,7 @@ def process_stake_data(stake_data: list) -> dict: return decoded_stake_data -def decode_metadata(metadata: dict) -> str: +def decode_metadata(metadata: "CommitmentOfResponse") -> str: commitment = metadata["info"]["fields"][0] if isinstance(commitment, str): return "" diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 48340e1668..5c27ae0e4a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -148,6 +148,8 @@ Weights, PositionResponse, NeuronCertificateResponse, + CommitmentOfResponse, + CrowdloansResponse, ) from bittensor.utils import ( Certificate, @@ -288,7 +290,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): def _decode_crowdloan_entry( self, crowdloan_id: int, - data: dict, + data: CrowdloansResponse, block_hash: Optional[str] = None, ) -> "CrowdloanInfo": """ @@ -299,6 +301,7 @@ def _decode_crowdloan_entry( call_data = data.get("call") if call_data and "Inline" in call_data: try: + # TODO need a working crowdloan call to see what this actually is, but I probably need to just remove this section inline_bytes = bytes(call_data["Inline"][0][0]) decoded_call = self.substrate.create_scale_object( type_string="Call", @@ -861,10 +864,10 @@ def blocks_since_last_step( Notes: - """ - query = self.query_subtensor( + query: ScaleType[int] = self.query_subtensor( name="BlocksSinceLastStep", block=block, params=[netuid] ) - return cast(Optional[int], getattr(query, "value", query)) + return query.value def blocks_since_last_update( self, netuid: int, uid: int, block: Optional[int] = None @@ -880,10 +883,11 @@ def blocks_since_last_update( The number of blocks since the last update, or None if the subnetwork or UID does not exist. """ block = block or self.get_current_block() - call = self.get_hyperparameter( + call: Optional[list[int]] = self.get_hyperparameter( param_name="LastUpdate", netuid=netuid, block=block ) - return None if not call else (block - int(call[uid])) + assert call is not None + return None if len(call) == 0 else (block - int(call[uid])) def blocks_until_next_epoch( self, netuid: int, tempo: Optional[int] = None, block: Optional[int] = None @@ -953,8 +957,10 @@ def bonds( block_hash=self.determine_block_hash(block), ) bond_map = [] + uid: int + bond: list[tuple[int, int]] for uid, bond in b_map_encoded: - if bond.value is not None: + if len(bond) != 0: bond_map.append((uid, bond)) return bond_map @@ -973,12 +979,13 @@ def commit_reveal_enabled(self, netuid: int, block: Optional[int] = None) -> boo - - """ - call = self.get_hyperparameter( + call: Optional[bool] = self.get_hyperparameter( param_name="CommitRevealWeightsEnabled", block=block, netuid=netuid ) - return True if call is True else False + assert call is not None + return call - def difficulty(self, netuid: int, block: Optional[int] = None) -> Optional[int]: + def difficulty(self, netuid: int, block: Optional[int] = None) -> int: """Retrieves the 'Difficulty' hyperparameter for a specified subnet in the Bittensor network. This parameter determines the computational challenge required for neurons to participate in consensus and @@ -998,11 +1005,10 @@ def difficulty(self, netuid: int, block: Optional[int] = None) -> Optional[int]: - - """ - call = self.get_hyperparameter( + call: Optional[int] = self.get_hyperparameter( param_name="Difficulty", netuid=netuid, block=block ) - if call is None: - return None + assert call is not None return int(call) def does_hotkey_exist(self, hotkey_ss58: str, block: Optional[int] = None) -> bool: @@ -1024,19 +1030,13 @@ def does_hotkey_exist(self, hotkey_ss58: str, block: Optional[int] = None) -> bo Notes: - """ - result = self.substrate.query( + result: ScaleType[str] = self.substrate.query( module="SubtensorModule", storage_function="Owner", params=[hotkey_ss58], block_hash=self.determine_block_hash(block), ) - return_val = ( - False - if result is None - # not the default key (0x0) - else result != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" - ) - return return_val + return result.value != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" def get_admin_freeze_window(self, block: Optional[int] = None) -> int: """Returns the duration, in blocks, of the administrative freeze window at the end of each epoch. @@ -1055,14 +1055,14 @@ def get_admin_freeze_window(self, block: Optional[int] = None) -> int: - """ - query = self.substrate.query( + query: ScaleType[int] = self.substrate.query( module="SubtensorModule", storage_function="AdminFreezeWindow", block_hash=self.determine_block_hash(block), ) - return cast(int, getattr(query, "value", query)) + return query.value - def get_all_subnets_info(self, block: Optional[int] = None) -> list["SubnetInfo"]: + def get_all_subnets_info(self, block: Optional[int] = None) -> list[SubnetInfo]: """Retrieves detailed information about all subnets within the Bittensor network. Parameters: @@ -1116,6 +1116,8 @@ def get_all_commitments( block=block, ) result = {} + id_: str + value: CommitmentOfResponse for id_, value in query: try: result[id_] = decode_metadata(value) @@ -1825,8 +1827,9 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> ) return "" - metadata = cast(dict, self.get_commitment_metadata(netuid, hotkey, block)) + metadata = self.get_commitment_metadata(netuid, hotkey, block) try: + assert not isinstance(metadata, str) return decode_metadata(metadata) except Exception as error: logging.error(error) @@ -1834,7 +1837,7 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> def get_commitment_metadata( self, netuid: int, hotkey_ss58: str, block: Optional[int] = None - ) -> Union[str, dict]: + ) -> str | CommitmentOfResponse: # TODO: how to handle return data? need good example @roman """Fetches raw commitment metadata from specific subnet for given hotkey. @@ -1850,21 +1853,21 @@ def get_commitment_metadata( Notes: - """ - commit_data = self.substrate.query( + commit_data: ScaleType[Optional[CommitmentOfResponse]] = self.substrate.query( module="Commitments", storage_function="CommitmentOf", params=[netuid, hotkey_ss58], block_hash=self.determine_block_hash(block), ) - if commit_data is None: + if commit_data.value is None: return "" - return cast(Union[str, dict], getattr(commit_data, "value", commit_data)) + return commit_data.value def get_crowdloan_constants( self, constants: Optional[list[str]] = None, block: Optional[int] = None, - ) -> "CrowdloanConstants": + ) -> CrowdloanConstants: """Retrieves runtime configuration constants governing crowdloan behavior and limits on the Bittensor blockchain. If a list of constant names is provided, only those constants will be queried. @@ -1911,7 +1914,7 @@ def get_crowdloan_contributions( self, crowdloan_id: int, block: Optional[int] = None, - ) -> dict[str, "Balance"]: + ) -> dict[str, Balance]: """Retrieves all contributions made to a specific crowdloan campaign. Returns a mapping of contributor coldkey addresses to their contribution amounts in Rao. @@ -1938,14 +1941,16 @@ def get_crowdloan_contributions( block_hash=block_hash, ) result = {} - for contributor, amount in query.records: + contributor: str + amount: int + for contributor, amount in query: if amount: result[contributor] = Balance.from_rao(amount) return result def get_crowdloan_by_id( self, crowdloan_id: int, block: Optional[int] = None - ) -> Optional["CrowdloanInfo"]: + ) -> Optional[CrowdloanInfo]: """Retrieves detailed information about a specific crowdloan campaign. Parameters: @@ -1963,13 +1968,13 @@ def get_crowdloan_by_id( - Crowdloans Overview: """ block_hash = self.determine_block_hash(block) - query = self.substrate.query( + query: ScaleType[Optional[CrowdloansResponse]] = self.substrate.query( module="Crowdloan", storage_function="Crowdloans", params=[crowdloan_id], block_hash=block_hash, ) - if not query: + if not query.value: return None return self._decode_crowdloan_entry( crowdloan_id=crowdloan_id, data=query.value, block_hash=block_hash @@ -1995,13 +2000,12 @@ def get_crowdloan_next_id( - Crowdloan Tutorial: """ block_hash = self.determine_block_hash(block) - result = self.substrate.query( + result: ScaleType[int] = self.substrate.query( module="Crowdloan", storage_function="NextCrowdloanId", block_hash=block_hash, ) - value = cast(int, getattr(result, "value", result)) - return int(value) or 0 + return result.value def get_crowdloans( self, @@ -2699,10 +2703,10 @@ def get_mev_shield_current_key( block_hash=block_hash, ) - if query.value is None: + if query.value_object is None: return None - value: bytearray = query.value + value: bytearray = query.value_object public_key_bytes = bytes(value) @@ -2739,10 +2743,10 @@ def get_mev_shield_next_key(self, block: Optional[int] = None) -> Optional[bytes block_hash=block_hash, ) - if query.value is None: + if query.value_object is None: return None - value: bytearray = query.value + value: bytearray = query.value_object public_key_bytes = bytes(value) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 975f120ee2..18d9a9cb9f 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -592,3 +592,27 @@ class PositionResponse(TypedDict): class NeuronCertificateResponse(TypedDict): public_key: str algorithm: int + + +class _CommitmentFields(TypedDict): + fields: list[dict[str, str]] + + +class CommitmentOfResponse(TypedDict): + deposit: int + block: int + info: _CommitmentFields + + +class CrowdloansResponse(TypedDict): + creator: str + deposit: int + min_contribution: int + end: int + cap: int + funds_account: str + raised: int + target_address: str + call: Optional[dict] + finalized: bool + contributors_count: int From ddaaf294b3ac6b721606931f3ac34cf5b0a984f3 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 18:16:26 +0200 Subject: [PATCH 18/38] Checkin --- bittensor/core/async_subtensor.py | 81 +++++++++++++++---------------- bittensor/core/subtensor.py | 39 ++++++++------- 2 files changed, 57 insertions(+), 63 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 14fe9382db..8b877abd93 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -528,7 +528,7 @@ async def _query_with_fallback( *args: tuple[str, str, Optional[list[Any]]], block_hash: Optional[str] = None, default_value: Any = ValueError, - ): + ) -> ScaleType[Any] | Any: """ Queries the subtensor node with a given set of args, falling back to the next group if the method does not exist at the given block. This method exists to support backwards compatibility for blocks. @@ -2490,17 +2490,14 @@ async def get_crowdloans( ) crowdloans = [] - - if query.records: - async for c_id, value_obj in query: - data = value_obj - if not data: - continue - crowdloans.append( - await self._decode_crowdloan_entry( - crowdloan_id=c_id, data=data, block_hash=block_hash - ) + c_id: int + data: CrowdloansResponse + async for c_id, data in query: + crowdloans.append( + await self._decode_crowdloan_entry( + crowdloan_id=c_id, data=data, block_hash=block_hash ) + ) return crowdloans @@ -2608,14 +2605,14 @@ async def get_delegate_take( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.query_subtensor( + result: ScaleType[int] = await self.query_subtensor( name="Delegates", block_hash=block_hash, reuse_block=reuse_block, params=[hotkey_ss58], ) - return u16_normalized_float(result.value) # type: ignore + return u16_normalized_float(result.value) async def get_delegated( self, @@ -2721,7 +2718,7 @@ async def get_existential_deposit( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.get_constant( + result: Optional[ScaleType[int]] = await self.substrate.get_constant( module_name="Balances", constant_name="ExistentialDeposit", block_hash=block_hash, @@ -2730,7 +2727,7 @@ async def get_existential_deposit( if result is None: raise Exception("Unable to retrieve existential deposit amount.") - return Balance.from_rao(getattr(result, "value", 0)) + return Balance.from_rao(result.value) async def get_ema_tao_inflow( self, @@ -2761,7 +2758,7 @@ async def get_ema_tao_inflow( - EMA smoothing: """ block_hash = await self.determine_block_hash(block) - query = await self.substrate.query( + query: ScaleType[Optional[tuple[int, FixedPoint]]] = await self.substrate.query( module="SubtensorModule", storage_function="SubnetEmaTaoFlow", params=[netuid], @@ -2769,11 +2766,12 @@ async def get_ema_tao_inflow( ) # sn0 doesn't have EmaTaoInflow - if query is None: + if query.value is None: return None block_updated, tao_bits = query.value ema_value = int(fixed_to_float(tao_bits)) + # TODO verify this from rao, seems like we're just rounding down return block_updated, Balance.from_rao(ema_value) async def get_hotkey_owner( @@ -2803,17 +2801,15 @@ async def get_hotkey_owner( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - hk_owner_query = await self.substrate.query( + hk_owner: ScaleType[str] = await self.substrate.query( module="SubtensorModule", storage_function="Owner", params=[hotkey_ss58], block_hash=block_hash, ) - exists = False - if hk_owner_query: - exists = await self.does_hotkey_exist(hotkey_ss58, block_hash=block_hash) - hotkey_owner = hk_owner_query if exists else None - return cast(Optional[str], getattr(hotkey_owner, "value", hotkey_owner)) + exists = await self.does_hotkey_exist(hotkey_ss58, block_hash=block_hash) + hotkey_owner = hk_owner.value if exists else None + return hotkey_owner async def get_last_bonds_reset( self, @@ -2822,7 +2818,7 @@ async def get_last_bonds_reset( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> ScaleType[int]: + ) -> ScaleType[Optional[int]]: """Retrieves the block number when bonds were last reset for a specific hotkey on a subnet. Parameters: @@ -2833,14 +2829,15 @@ async def get_last_bonds_reset( reuse_block: Whether to use the last-used block. Do not set if using `block_hash` or `block`. Returns: - The block number when bonds were last reset, or `None` if no bonds reset has occurred. + A ScaleType object containing the block number when bonds were last reset, or `None` if no bonds reset + has occurred. Notes: - - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - block = await self.substrate.query( + block: ScaleType[Optional[int]] = await self.substrate.query( module="Commitments", storage_function="LastBondsReset", params=[netuid, hotkey_ss58], @@ -2881,7 +2878,7 @@ async def get_last_commitment_bonds_reset_block( block_data = await self.get_last_bonds_reset( netuid, hotkey, block, block_hash, reuse_block ) - return getattr(block_data, "value", None) + return block_data.value async def get_liquidity_list( self, @@ -3000,7 +2997,7 @@ async def get_liquidity_list( ) # iterator with just the values ticks = iter([x[1] for x in ticks_query]) - positions = [] + positions: list[LiquidityPosition] = [] for position, tick_low_idx, tick_high_idx in positions_values: tick_low = next(ticks) tick_high = next(ticks) @@ -3056,19 +3053,15 @@ async def get_liquidity_list( positions.append( LiquidityPosition( - **{ - "id": position.get("id"), - "price_low": Balance.from_tao( - tick_to_price(position.get("tick_low")) - ), - "price_high": Balance.from_tao( - tick_to_price(position.get("tick_high")) - ), - "liquidity": Balance.from_rao(position.get("liquidity")), - "fees_tao": fees_tao, - "fees_alpha": fees_alpha, - "netuid": position.get("netuid"), - } + id=position.get("id"), + price_low=Balance.from_tao(tick_to_price(position.get("tick_low"))), + price_high=Balance.from_tao( + tick_to_price(position.get("tick_high")) + ), + liquidity=Balance.from_rao(position.get("liquidity")), + fees_tao=fees_tao, + fees_alpha=fees_alpha, + netuid=position.get("netuid"), ) ) @@ -3097,12 +3090,14 @@ async def get_mechanism_emission_split( - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result: ScaleType[Optional[list[int]]] = await self._query_with_fallback( + result: Optional[ + ScaleType[Optional[list[int]]] + ] = await self._query_with_fallback( ("SubtensorModule", "MechanismEmissionSplit", [netuid]), block_hash=block_hash, default_value=None, ) - if result.value is None: + if result is None or result.value is None: return None total = sum(result.value) return [round(i / total * 100) for i in result.value] diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 5c27ae0e4a..9afa8a3ce5 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2037,11 +2037,9 @@ def get_crowdloans( ) crowdloans = [] - - for c_id, value_obj in getattr(query, "records", []): - data = value_obj.value - if not data: - continue + c_id: int + data: CrowdloansResponse + for c_id, data in query: crowdloans.append( self._decode_crowdloan_entry( crowdloan_id=c_id, data=data, block_hash=block_hash @@ -2169,7 +2167,7 @@ def get_delegated( return DelegatedInfo.list_from_dicts(result) - def get_delegates(self, block: Optional[int] = None) -> list["DelegateInfo"]: + def get_delegates(self, block: Optional[int] = None) -> list[DelegateInfo]: """Fetches all delegates registered on the chain. Delegates are validators that accept stake from other TAO holders (nominators/delegators). This method @@ -2197,7 +2195,7 @@ def get_delegates(self, block: Optional[int] = None) -> list["DelegateInfo"]: else: return [] - def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balance]: + def get_existential_deposit(self, block: Optional[int] = None) -> Balance: """Retrieves the existential deposit amount for the Bittensor blockchain. The existential deposit is the minimum amount of TAO required for an account to exist on the blockchain. @@ -2213,7 +2211,7 @@ def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balan Notes: - """ - result = self.substrate.get_constant( + result: Optional[ScaleType[int]] = self.substrate.get_constant( module_name="Balances", constant_name="ExistentialDeposit", block_hash=self.determine_block_hash(block), @@ -2222,7 +2220,7 @@ def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balan if result is None: raise Exception("Unable to retrieve existential deposit amount.") - return Balance.from_rao(getattr(result, "value", 0)) + return Balance.from_rao(result.value) def get_ema_tao_inflow( self, @@ -2266,6 +2264,7 @@ def get_ema_tao_inflow( block_updated, tao_bits = query.value ema_value = int(fixed_to_float(tao_bits)) + # TODO verify this from rao, seems like we're just rounding down return block_updated, Balance.from_rao(ema_value) def get_hotkey_owner( @@ -2283,21 +2282,19 @@ def get_hotkey_owner( Returns: The SS58 address of the owner if the hotkey exists, or `None` if it doesn't. """ - hk_owner_query = self.substrate.query( + hk_owner: ScaleType[str] = self.substrate.query( module="SubtensorModule", storage_function="Owner", params=[hotkey_ss58], block_hash=self.determine_block_hash(block), ) - exists = False - if hk_owner_query: - exists = self.does_hotkey_exist(hotkey_ss58, block=block) - hotkey_owner = hk_owner_query if exists else None - return cast(Optional[str], getattr(hotkey_owner, "value", hotkey_owner)) + exists = self.does_hotkey_exist(hotkey_ss58, block=block) + hotkey_owner = hk_owner.value if exists else None + return hotkey_owner def get_last_bonds_reset( self, netuid: int, hotkey_ss58: str, block: Optional[int] = None - ): + ) -> ScaleType[Optional[int]]: """Retrieves the block number when bonds were last reset for a specific hotkey on a subnet. Parameters: @@ -2306,18 +2303,20 @@ def get_last_bonds_reset( block: The block number to query. If `None`, queries the current chain head. Returns: - The block number when bonds were last reset, or `None` if no bonds reset has occurred. + A ScaleType object containing the block number when bonds were last reset, or `None` if no bonds reset + has occurred. Notes: - - """ - return self.substrate.query( + block_: ScaleType[Optional[int]] = self.substrate.query( module="Commitments", storage_function="LastBondsReset", params=[netuid, hotkey_ss58], block_hash=self.determine_block_hash(block), ) + return block_ def get_last_commitment_bonds_reset_block( self, @@ -2346,7 +2345,7 @@ def get_last_commitment_bonds_reset_block( ) return None block_data = self.get_last_bonds_reset(netuid, hotkey_ss58, block) - return getattr(block_data, "value", None) + return block_data.value def get_liquidity_list( self, @@ -2448,7 +2447,7 @@ def get_liquidity_list( ) # iterator with just the values ticks = iter([x[1] for x in ticks_query]) - positions = [] + positions: list[LiquidityPosition] = [] for position, tick_low_idx, tick_high_idx in positions_values: tick_low = next(ticks) tick_high = next(ticks) From d02a096aebe73e9ec6cbfb68722a900c51239695 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 18:20:31 +0200 Subject: [PATCH 19/38] Checkin --- bittensor/core/async_subtensor.py | 9 ++++----- bittensor/core/subtensor.py | 7 +++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 8b877abd93..a9823baf46 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3326,7 +3326,7 @@ async def get_mev_shield_next_key( return public_key_bytes - async def get_minimum_required_stake(self): + async def get_minimum_required_stake(self) -> Balance: """Returns the minimum required stake threshold for nominator cleanup operations. This threshold is used ONLY for cleanup after unstaking operations. If a nominator's remaining stake @@ -3379,6 +3379,8 @@ async def get_netuids_for_hotkey( block_hash=block_hash, ) netuids = [] + netuid: int + is_member: bool if result.records: async for netuid, is_member in result: if is_member: @@ -3423,10 +3425,7 @@ async def get_neuron_certificate( ) certificate: Optional[NeuronCertificateResponse] = certificate_query.value if certificate is not None: - try: - return Certificate(certificate) - except AttributeError: - return None + return Certificate(certificate) return None async def get_neuron_for_pubkey_and_subnet( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 9afa8a3ce5..77e746b588 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2804,6 +2804,8 @@ def get_netuids_for_hotkey( block_hash=self.determine_block_hash(block), ) netuids = [] + netuid: int + is_member: bool if result.records: for netuid, is_member in result: if is_member: @@ -2838,10 +2840,7 @@ def get_neuron_certificate( ) certificate: Optional[str | NeuronCertificateResponse] = certificate_query.value if certificate is not None: - try: - return Certificate(certificate) - except AttributeError: - return None + return Certificate(certificate) return None def get_neuron_for_pubkey_and_subnet( From 3a9e0c0cf36b1d645bee77f2b2c9a79bf96b320d Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 20:31:44 +0200 Subject: [PATCH 20/38] Async unit tests passing --- bittensor/core/async_subtensor.py | 7 +- bittensor/core/subtensor.py | 5 +- .../chain_data/test_coldkey_swap.py | 14 +- tests/unit_tests/test_async_subtensor.py | 178 +++++++++--------- 4 files changed, 101 insertions(+), 103 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index a9823baf46..ff0f1bec8a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2807,7 +2807,10 @@ async def get_hotkey_owner( params=[hotkey_ss58], block_hash=block_hash, ) - exists = await self.does_hotkey_exist(hotkey_ss58, block_hash=block_hash) + if hk_owner.value != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": + exists = await self.does_hotkey_exist(hotkey_ss58, block_hash=block_hash) + else: + exists = False hotkey_owner = hk_owner.value if exists else None return hotkey_owner @@ -5587,7 +5590,7 @@ async def query_identity( ) identity_data: Optional[dict[str, Any]] = identity_info.value - if identity_info is None: + if identity_data is None: return None return ChainIdentity.from_dict(decode_hex_identity_dict(identity_data)) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 77e746b588..d3ae97eb45 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2288,7 +2288,10 @@ def get_hotkey_owner( params=[hotkey_ss58], block_hash=self.determine_block_hash(block), ) - exists = self.does_hotkey_exist(hotkey_ss58, block=block) + if hk_owner.value != "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": + exists = self.does_hotkey_exist(hotkey_ss58, block=block) + else: + exists = False hotkey_owner = hk_owner.value if exists else None return hotkey_owner diff --git a/tests/unit_tests/chain_data/test_coldkey_swap.py b/tests/unit_tests/chain_data/test_coldkey_swap.py index db93a8ee6e..f5a2aa33b6 100644 --- a/tests/unit_tests/chain_data/test_coldkey_swap.py +++ b/tests/unit_tests/chain_data/test_coldkey_swap.py @@ -10,7 +10,7 @@ def test_coldkey_swap_announcement_info_from_query_none(mocker): """Test from_query returns None when query has no value.""" # Prep coldkey_ss58 = mocker.Mock(spec=str) - query = None + query = mocker.Mock(spec=ScaleType, value=None) # Call from_query = ColdkeySwapAnnouncementInfo.from_query(coldkey_ss58, query) @@ -24,23 +24,17 @@ def test_coldkey_swap_announcement_info_from_query_happy_path(mocker): # Prep coldkey_ss58 = mocker.Mock(spec=str) fake_block = mocker.Mock(spec=int) - fake_hash_data = mocker.Mock(spec=list) - query = mocker.Mock(value=(fake_block, (fake_hash_data,))) - - mocked_bytes = mocker.patch("bittensor.core.chain_data.coldkey_swap.bytes") + fake_hash_data = mocker.Mock(spec=str) + query = mocker.Mock(value=(fake_block, fake_hash_data)) # Call from_query = ColdkeySwapAnnouncementInfo.from_query(coldkey_ss58, query) # Asserts - mocked_bytes.assert_called_once_with(fake_hash_data) assert from_query is not None, "Should return ColdkeySwapAnnouncementInfo object" assert from_query.coldkey == coldkey_ss58 assert from_query.execution_block == fake_block - assert ( - from_query.new_coldkey_hash - == mocked_bytes.return_value.hex.return_value.__radd__.return_value - ) + assert from_query.new_coldkey_hash == fake_hash_data def test_coldkey_swap_dispute_info_from_query_none(mocker): diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index dda5957a91..9e4fadfe3b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -583,7 +583,7 @@ async def test_get_balance(subtensor, mocker): block_hash=mocked_determine_block_hash.return_value, ) mocked_balance.assert_called_once_with( - subtensor.substrate.query.return_value.__getitem__.return_value.__getitem__.return_value + subtensor.substrate.query.return_value.value.__getitem__.return_value.__getitem__.return_value ) assert result == mocked_balance.return_value @@ -1050,7 +1050,7 @@ async def test_get_neuron_for_pubkey_and_subnet_uid_not_found(subtensor, mocker) mocker.patch.object( subtensor.substrate, "query", - return_value=None, + return_value=mocker.Mock(spec=ScaleType, value=None), ) mocked_get_null_neuron = mocker.patch.object( async_subtensor.NeuronInfo, "get_null_neuron", return_value="null_neuron" @@ -1361,38 +1361,11 @@ async def test_query_identity_no_info(subtensor, mocker): # Preps fake_coldkey_ss58 = "test_key" - mocked_query = mocker.AsyncMock(return_value=None) - subtensor.substrate.query = mocked_query - - # Call - result = await subtensor.query_identity(coldkey_ss58=fake_coldkey_ss58) - - # Asserts - mocked_query.assert_called_once_with( - module="SubtensorModule", - storage_function="IdentitiesV2", - params=[fake_coldkey_ss58], - block_hash=None, + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value=None) ) - assert result is None - - -@pytest.mark.asyncio -async def test_query_identity_type_error(subtensor, mocker): - """Tests query_identity method when a TypeError occurs during decoding.""" - # Preps - fake_coldkey_ss58 = "test_key" - fake_identity_info = {"info": {"rank": (b"\xff\xfe",)}} - - mocked_query = mocker.AsyncMock(return_value=fake_identity_info) subtensor.substrate.query = mocked_query - mocker.patch.object( - async_subtensor, - "decode_hex_identity_dict", - side_effect=TypeError, - ) - # Call result = await subtensor.query_identity(coldkey_ss58=fake_coldkey_ss58) @@ -1497,7 +1470,9 @@ async def test_does_hotkey_exist_false_for_specific_account(subtensor, mocker): """Tests does_hotkey_exist method when the hotkey exists but matches the specific account ID to ignore.""" # Preps fake_hotkey_ss58 = "fake_hotkey" - fake_query_result = "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" + fake_query_result = mocker.Mock( + spec=ScaleType, value="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" + ) mocked_query = mocker.patch.object( subtensor.substrate, "query", return_value=fake_query_result @@ -1523,7 +1498,9 @@ async def test_get_hotkey_owner_successful(subtensor, mocker): fake_hotkey_ss58 = "valid_hotkey" fake_block_hash = "block_hash" - mocked_query = mocker.AsyncMock(return_value="decoded_owner_account_id") + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value="decoded_owner_account_id") + ) subtensor.substrate.query = mocked_query mocked_does_hotkey_exist = mocker.AsyncMock(return_value=True) @@ -1554,7 +1531,11 @@ async def test_get_hotkey_owner_non_existent_hotkey(subtensor, mocker): fake_hotkey_ss58 = "non_existent_hotkey" fake_block_hash = "block_hash" - mocked_query = mocker.AsyncMock(return_value=None) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock( + spec=ScaleType, value="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" + ) + ) subtensor.substrate.query = mocked_query # Call @@ -1939,7 +1920,9 @@ async def test_get_parents_no_parents(subtensor, mocker): fake_netuid = 1 fake_parents = [] - mocked_query = mocker.AsyncMock(return_value=fake_parents) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value=fake_parents) + ) subtensor.substrate.query = mocked_query # Call @@ -2100,7 +2083,9 @@ async def test_get_vote_data_success(subtensor, mocker): fake_block_hash = "block_hash" fake_vote_data = {"ayes": ["senate_member_1"], "nays": ["senate_member_2"]} - mocked_query = mocker.AsyncMock(return_value=fake_vote_data) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value=fake_vote_data) + ) subtensor.substrate.query = mocked_query mocked_proposal_vote_data = mocker.Mock() @@ -2132,7 +2117,9 @@ async def test_get_vote_data_no_data(subtensor, mocker): fake_proposal_hash = "invalid_proposal_hash" fake_block_hash = "block_hash" - mocked_query = mocker.AsyncMock(return_value=None) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value=None) + ) subtensor.substrate.query = mocked_query # Call @@ -2404,7 +2391,7 @@ async def test_blocks_since_last_update_no_last_update(subtensor, mocker): # Preps fake_netuid = 1 fake_uid = 5 - fake_result = None + fake_result = [] mocked_get_hyperparameter = mocker.patch.object( subtensor, @@ -2436,7 +2423,9 @@ async def test_commit_reveal_enabled(subtensor, mocker): netuid = 1 block_hash = "block_hash" mocked_get_hyperparameter = mocker.patch.object( - subtensor, "get_hyperparameter", return_value=mocker.AsyncMock() + subtensor, + "get_hyperparameter", + return_value=False, ) # Call @@ -2756,7 +2745,9 @@ async def test_get_owned_hotkeys_happy_path(subtensor, mocker): fake_coldkey = "fake_hotkey" fake_hotkey = "fake_hotkey" fake_hotkeys = [fake_hotkey] - mocked_subtensor = mocker.AsyncMock(return_value=fake_hotkeys) + mocked_subtensor = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value=fake_hotkeys) + ) mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor) # Call @@ -2777,7 +2768,9 @@ async def test_get_owned_hotkeys_return_empty(subtensor, mocker): """Tests that the output of get_owned_hotkeys is empty.""" # Prep fake_coldkey = "fake_hotkey" - mocked_subtensor = mocker.AsyncMock(return_value=[]) + mocked_subtensor = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value=[]) + ) mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor) # Call @@ -2895,7 +2888,7 @@ async def test_get_metagraph_info_all_fields(subtensor, mocker): mock_runtime_call = mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.AsyncMock(value=mock_value), + return_value=mock_value, ) mock_chain_head = mocker.patch.object( subtensor.substrate, @@ -2949,7 +2942,7 @@ async def test_get_metagraph_info_specific_fields(subtensor, mocker): mock_runtime_call = mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.AsyncMock(value=mock_value), + return_value=mock_value, ) mock_chain_head = mocker.patch.object( subtensor.substrate, @@ -3120,7 +3113,9 @@ async def test_blocks_since_last_step_is_none(subtensor, mocker): # preps netuid = 1 block = 123 - mocked_query_subtensor = mocker.AsyncMock(return_value=None) + mocked_query_subtensor = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value=None) + ) subtensor.query_subtensor = mocked_query_subtensor # call @@ -3480,10 +3475,10 @@ async def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): [ (2,), { - "id": (2,), + "id": 2, "netuid": 2, - "tick_low": (206189,), - "tick_high": (208196,), + "tick_low": 206189, + "tick_high": 208196, "liquidity": 1000000000000, "fees_tao": {"bits": 0}, "fees_alpha": {"bits": 0}, @@ -3492,10 +3487,10 @@ async def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): [ 2, { - "id": (2,), + "id": 2, "netuid": 2, - "tick_low": (216189,), - "tick_high": (198196,), + "tick_low": 216189, + "tick_high": 198196, "liquidity": 2000000000000, "fees_tao": {"bits": 0}, "fees_alpha": {"bits": 0}, @@ -3504,10 +3499,10 @@ async def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): [ 2, { - "id": (2,), + "id": 2, "netuid": 2, - "tick_low": (226189,), - "tick_high": (188196,), + "tick_low": 226189, + "tick_high": 188196, "liquidity": 3000000000000, "fees_tao": {"bits": 0}, "fees_alpha": {"bits": 0}, @@ -3756,11 +3751,8 @@ async def test_all_subnets(subtensor, mocker): "get_subnet_prices", return_value={0: Balance.from_tao(1), 1: Balance.from_tao(0.029258617)}, ) - mocked_decode = mocker.Mock(return_value=[{"netuid": 0}, {"netuid": 1}]) - mocked_runtime_call = mocker.Mock(decode=mocked_decode) - mocker.patch.object( - subtensor.substrate, "runtime_call", return_value=mocked_runtime_call - ) + mocked_decode = [{"netuid": 0}, {"netuid": 1}] + mocker.patch.object(subtensor.substrate, "runtime_call", return_value=mocked_decode) # Call result = await subtensor.all_subnets() @@ -4554,12 +4546,10 @@ async def test_get_crowdloan_contributions(mocker, subtensor): async def test_get_crowdloan_by_id(mocker, subtensor, query_return, expected_result): """Tests subtensor `get_crowdloan_by_id` method.""" # Preps - fake_crowdloan_id = mocker.Mock(spec=int) + fake_crowdloan_id = mocker.Mock(spec=ScaleType, value=mocker.Mock(spec=int)) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_query_return = ( - None if query_return is None else mocker.Mock(value=query_return) - ) + mocked_query_return = mocker.Mock(value=query_return) mocked_query = mocker.patch.object( subtensor.substrate, "query", return_value=mocked_query_return ) @@ -4698,23 +4688,21 @@ async def test_commit_weights_with_zero_max_attempts( @pytest.mark.parametrize( "fake_result, expected_result", [ - ({"Swap": ()}, "Swap"), - ({"Keep": ()}, "Keep"), + ("Swap", "Swap"), + ("Keep", "Keep"), ( { "KeepSubnets": { - "subnets": ( - ( - 2, - 3, - ), - ) + "subnets": [ + 2, + 3, + ], } }, {"KeepSubnets": {"subnets": [2, 3]}}, ), ( - {"KeepSubnets": {"subnets": ((2,),)}}, + {"KeepSubnets": {"subnets": [2]}}, { "KeepSubnets": { "subnets": [ @@ -4734,7 +4722,9 @@ async def test_get_root_claim_type(mocker, subtensor, fake_result, expected_resu fake_coldkey_ss58 = mocker.Mock(spec=str) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_map = mocker.patch.object( - subtensor.substrate, "query", return_value=fake_result + subtensor.substrate, + "query", + return_value=mocker.Mock(spec=ScaleType, value=fake_result), ) # call @@ -4782,8 +4772,8 @@ async def test_get_root_claimable_all_rates(mocker, subtensor): # Preps hotkey_ss58 = mocker.Mock(spec=str) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - fake_value = [((14, {"bits": 6520190}),)] - fake_result = mocker.MagicMock(value=fake_value) + fake_value = [(14, {"bits": 6520190})] + fake_result = mocker.MagicMock(spec=ScaleType, value=fake_value) fake_result.__iter__ = fake_value mocked_query = mocker.patch.object( subtensor.substrate, "query", return_value=fake_result @@ -5128,7 +5118,7 @@ async def test_get_proxy_announcement(subtensor, mocker): params=[fake_delegate_account_ss58], block_hash=mocked_determine_block_hash.return_value, ) - mocked_from_dict.assert_called_once_with(mocked_query.return_value.value[0]) + mocked_from_dict.assert_called_once_with(mocked_query.return_value.value) assert result == mocked_from_dict.return_value @@ -5772,12 +5762,13 @@ async def test_get_mev_shield_current_key_success(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + fake_public_key_bytes = bytearray(b"\x00" * 1184) # ML-KEM-768 public key size mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) - mocked_query = mocker.AsyncMock() - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value_object=fake_public_key_bytes) + ) subtensor.substrate.query = mocked_query # Call @@ -5802,7 +5793,9 @@ async def test_get_mev_shield_current_key_none(subtensor, mocker): mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) - mocked_query = mocker.AsyncMock(return_value=None) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value_object=None) + ) subtensor.substrate.query = mocked_query # Call @@ -5824,12 +5817,13 @@ async def test_get_mev_shield_current_key_invalid_size(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1000 # Invalid size + fake_public_key_bytes = bytearray(b"\x00" * 1000) # Invalid size mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) - mocked_query = mocker.AsyncMock() - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value_object=fake_public_key_bytes) + ) subtensor.substrate.query = mocked_query # Call & Assert @@ -5851,12 +5845,13 @@ async def test_get_mev_shield_next_key_success(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + fake_public_key_bytes = bytearray(b"\x00" * 1184) # ML-KEM-768 public key size mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) - mocked_query = mocker.AsyncMock() - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value_object=fake_public_key_bytes) + ) subtensor.substrate.query = mocked_query # Call @@ -5881,7 +5876,9 @@ async def test_get_mev_shield_next_key_none(subtensor, mocker): mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) - mocked_query = mocker.AsyncMock(return_value=None) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value_object=None) + ) subtensor.substrate.query = mocked_query # Call @@ -5903,12 +5900,13 @@ async def test_get_mev_shield_next_key_invalid_size(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1000 # Invalid size + fake_public_key_bytes = bytearray(b"\x00" * 1000) # Invalid size mocked_determine_block_hash = mocker.AsyncMock(return_value=fake_block_hash) mocker.patch.object(subtensor, "determine_block_hash", mocked_determine_block_hash) - mocked_query = mocker.AsyncMock() - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query = mocker.AsyncMock( + return_value=mocker.Mock(spec=ScaleType, value_object=fake_public_key_bytes) + ) subtensor.substrate.query = mocked_query # Call & Assert @@ -6039,7 +6037,7 @@ async def test_get_start_call_delay(subtensor, mocker): block_hash=None, reuse_block=False, ) - assert result == mocked_query_subtensor.return_value + assert result == mocked_query_subtensor.return_value.value @pytest.mark.asyncio From bd884a8867a445be890f01ec7eece4d5b059aee7 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 21:05:41 +0200 Subject: [PATCH 21/38] Sync unit tests passing --- tests/unit_tests/test_async_subtensor.py | 6 +- tests/unit_tests/test_subtensor.py | 219 +++++++++-------------- 2 files changed, 90 insertions(+), 135 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 9e4fadfe3b..fbd2ba0cb3 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4509,12 +4509,12 @@ async def test_get_crowdloan_constants(mocker, subtensor): async def test_get_crowdloan_contributions(mocker, subtensor): """Tests subtensor `get_crowdloan_contributions` method.""" # Preps - fake_hk_array = mocker.Mock(spec=list) + fake_hk = mocker.Mock(spec=str) fake_contribution = mocker.Mock(value=mocker.Mock(spec=Balance)) fake_crowdloan_id = mocker.Mock(spec=int) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - records = [(fake_hk_array, fake_contribution)] + records = [(fake_hk, fake_contribution)] fake_result = mocker.AsyncMock(autospec=list) fake_result.records = records fake_result.__aiter__.return_value = iter(records) @@ -4536,7 +4536,7 @@ async def test_get_crowdloan_contributions(mocker, subtensor): params=[fake_crowdloan_id], block_hash=mocked_determine_block_hash.return_value, ) - assert result == {fake_hk_array: mocked_from_rao.return_value} + assert result == {fake_hk: mocked_from_rao.return_value} @pytest.mark.parametrize( diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 44b178597f..94efccdc0d 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -41,6 +41,16 @@ def fake_call_params(): return call_params() +@pytest.fixture +def scale_type_none(mocker): + return mocker.Mock(spec=ScaleType, value=None) + + +@pytest.fixture +def scale_type(mocker): + return mocker.Mock(spec=ScaleType) + + def call_params(): return AxonServeCallParams( version=settings.version_as_int, @@ -291,10 +301,10 @@ def test_hyperparameter_subnet_does_not_exist(subtensor, mocker): subtensor.subnet_exists.assert_called_once_with(1, block=None) -def test_hyperparameter_result_is_none(subtensor, mocker): +def test_hyperparameter_result_is_none(subtensor, mocker, scale_type_none): """Tests when query_subtensor returns None.""" subtensor.subnet_exists = mocker.MagicMock(return_value=True) - subtensor.substrate.query = mocker.MagicMock(return_value=None) + subtensor.substrate.query = mocker.MagicMock(return_value=scale_type_none) assert subtensor.get_hyperparameter("Difficulty", 1, None) is None subtensor.subnet_exists.assert_called_once_with(1, block=None) subtensor.substrate.query.assert_called_once_with( @@ -305,10 +315,10 @@ def test_hyperparameter_result_is_none(subtensor, mocker): ) -def test_hyperparameter_result_has_no_value(subtensor, mocker): +def test_hyperparameter_result_has_no_value(subtensor, mocker, scale_type_none): """Test when the result has no 'value' attribute.""" subtensor.subnet_exists = mocker.MagicMock(return_value=True) - subtensor.substrate.query = mocker.MagicMock(return_value=None) + subtensor.substrate.query = mocker.MagicMock(return_value=scale_type_none) assert subtensor.get_hyperparameter("Difficulty", 1, None) is None subtensor.subnet_exists.assert_called_once_with(1, block=None) subtensor.substrate.query.assert_called_once_with( @@ -544,7 +554,9 @@ def test_commit_reveal_enabled(subtensor, mocker): # Preps netuid = 1 block = 123 - mocked_get_hyperparameter = mocker.patch.object(subtensor, "get_hyperparameter") + mocked_get_hyperparameter = mocker.patch.object( + subtensor, "get_hyperparameter", return_value=False + ) # Call result = subtensor.commit_reveal_enabled(netuid, block) @@ -1789,28 +1801,6 @@ def test_difficulty_success(subtensor, mocker): assert result == int(mocked_get_hyperparameter.return_value) -def test_difficulty_none(subtensor, mocker): - """Tests difficulty method with None result.""" - # Preps - mocked_get_hyperparameter = mocker.patch.object( - subtensor, "get_hyperparameter", return_value=None - ) - fake_netuid = 1 - fake_block = 2 - - # Call - result = subtensor.difficulty(fake_netuid, fake_block) - - # Asserts - mocked_get_hyperparameter.assert_called_once_with( - param_name="Difficulty", - netuid=fake_netuid, - block=fake_block, - ) - - assert result is None - - def test_recycle_success(subtensor, mocker): """Tests recycle method with successfully result.""" # Preps @@ -2024,43 +2014,19 @@ def test_does_hotkey_exist_true(mocker, subtensor): assert result is True -def test_does_hotkey_exist_no_value(mocker, subtensor): - """Test when query_subtensor returns no value.""" - # Mock data - fake_hotkey_ss58 = "fake_hotkey" - fake_block = 123 - - # Mocks - mock_query_subtensor = mocker.patch.object( - subtensor.substrate, "query", return_value=None - ) - - # Call - result = subtensor.does_hotkey_exist(fake_hotkey_ss58, block=fake_block) - - # Assertions - mock_query_subtensor.assert_called_once_with( - module="SubtensorModule", - storage_function="Owner", - params=[fake_hotkey_ss58], - block_hash=subtensor.substrate.get_block_hash.return_value, - ) - subtensor.substrate.get_block_hash.assert_called_once_with(fake_block) - assert result is False - - -def test_does_hotkey_exist_special_id(mocker, subtensor): +def test_does_hotkey_exist_special_id(mocker, subtensor, scale_type): """Test when query_subtensor returns the special invalid owner identifier.""" # Mock data fake_hotkey_ss58 = "fake_hotkey" fake_owner = "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM" fake_block = 123 + scale_type.value = fake_owner # Mocks mock_query_subtensor = mocker.patch.object( subtensor.substrate, "query", - return_value=fake_owner, + return_value=scale_type, ) # Call result = subtensor.does_hotkey_exist(fake_hotkey_ss58, block=fake_block) @@ -2102,16 +2068,17 @@ def test_does_hotkey_exist_latest_block(mocker, subtensor): assert result is True -def test_get_hotkey_owner_success(mocker, subtensor): +def test_get_hotkey_owner_success(mocker, subtensor, scale_type): """Test when hotkey exists and owner is found.""" # Mock data fake_hotkey_ss58 = "fake_hotkey" fake_coldkey_ss58 = "fake_coldkey" fake_block = 123 + scale_type.value = fake_coldkey_ss58 # Mocks mock_query_subtensor = mocker.patch.object( - subtensor.substrate, "query", return_value=fake_coldkey_ss58 + subtensor.substrate, "query", return_value=scale_type ) mock_does_hotkey_exist = mocker.patch.object( subtensor, "does_hotkey_exist", return_value=True @@ -2132,37 +2099,6 @@ def test_get_hotkey_owner_success(mocker, subtensor): assert result == fake_coldkey_ss58 -def test_get_hotkey_owner_no_value(mocker, subtensor): - """Test when query_subtensor returns no value.""" - # Mock data - fake_hotkey_ss58 = "fake_hotkey" - fake_block = 123 - - # Mocks - mock_query_subtensor = mocker.patch.object( - subtensor.substrate, - "query", - return_value=None, - ) - mock_does_hotkey_exist = mocker.patch.object( - subtensor, "does_hotkey_exist", return_value=True - ) - - # Call - result = subtensor.get_hotkey_owner(fake_hotkey_ss58, block=fake_block) - - # Assertions - mock_query_subtensor.assert_called_once_with( - module="SubtensorModule", - storage_function="Owner", - params=[fake_hotkey_ss58], - block_hash=subtensor.substrate.get_block_hash.return_value, - ) - mock_does_hotkey_exist.assert_not_called() - subtensor.substrate.get_block_hash.assert_called_once_with(fake_block) - assert result is None - - def test_get_hotkey_owner_does_not_exist(mocker, subtensor): """Test when hotkey does not exist.""" # Mock data @@ -2194,15 +2130,16 @@ def test_get_hotkey_owner_does_not_exist(mocker, subtensor): assert result is None -def test_get_hotkey_owner_latest_block(mocker, subtensor): +def test_get_hotkey_owner_latest_block(mocker, subtensor, scale_type): """Test when no block is provided (latest block).""" # Mock data fake_hotkey_ss58 = "fake_hotkey" fake_coldkey_ss58 = "fake_coldkey" + scale_type.value = fake_coldkey_ss58 # Mocks mock_query_subtensor = mocker.patch.object( - subtensor.substrate, "query", return_value=fake_coldkey_ss58 + subtensor.substrate, "query", return_value=scale_type ) mock_does_hotkey_exist = mocker.patch.object( subtensor, "does_hotkey_exist", return_value=True @@ -2283,7 +2220,7 @@ def test_get_minimum_required_stake_invalid_result(mocker, subtensor): mock_query.assert_called_once_with( module="SubtensorModule", storage_function="NominatorMinRequiredStake" ) - mock_balance_from_rao.assert_called_once_with(fake_invalid_stake) + mock_balance_from_rao.assert_called_once_with(0) assert result == mock_balance_from_rao.return_value @@ -3102,7 +3039,7 @@ def test_get_metagraph_info_all_fields(subtensor, mocker): mock_runtime_call = mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.Mock(value=mock_value), + return_value=mock_value, ) mock_chain_head = mocker.patch.object( subtensor.substrate, @@ -3155,7 +3092,7 @@ def test_get_metagraph_info_specific_fields(subtensor, mocker): mock_runtime_call = mocker.patch.object( subtensor.substrate, "runtime_call", - return_value=mocker.Mock(value=mock_value), + return_value=mock_value, ) mock_chain_head = mocker.patch.object( subtensor.substrate, @@ -3315,12 +3252,12 @@ def test_blocks_since_last_step_with_value(subtensor, mocker): assert result == mocked_query_subtensor.return_value.value -def test_blocks_since_last_step_is_none(subtensor, mocker): +def test_blocks_since_last_step_is_none(subtensor, mocker, scale_type_none): """Test blocks_since_last_step returns None correctly.""" # preps netuid = 1 block = 123 - mocked_query_subtensor = mocker.MagicMock(return_value=None) + mocked_query_subtensor = mocker.MagicMock(return_value=scale_type_none) subtensor.query_subtensor = mocked_query_subtensor # call @@ -3559,14 +3496,15 @@ def test_get_parents_success(subtensor, mocker): assert result == expected_formatted_parents -def test_get_parents_no_parents(subtensor, mocker): +def test_get_parents_no_parents(subtensor, mocker, scale_type): """Tests get_parents when there are no parents to retrieve.""" # Preps fake_hotkey = "valid_hotkey" fake_netuid = 1 fake_parents = [] + scale_type.value = fake_parents - mocked_query = mocker.MagicMock(return_value=fake_parents) + mocked_query = mocker.MagicMock(return_value=scale_type) subtensor.substrate.query = mocked_query # Call @@ -4652,9 +4590,14 @@ def test_get_crowdloan_contributions(mocker, subtensor): fake_contribution = mocker.Mock(spec=int) fake_crowdloan_id = mocker.Mock(spec=int) + records = [(fake_hk, fake_contribution)] + fake_result = mocker.MagicMock(autospec=list) + fake_result.records = records + fake_result.__iter__.return_value = iter(records) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_query_map = mocker.patch.object(subtensor.substrate, "query_map") - mocked_query_map.return_value.records = [(fake_hk, fake_contribution)] + mocked_query_map = mocker.patch.object( + subtensor.substrate, "query_map", return_value=fake_result + ) mocked_from_rao = mocker.patch.object(subtensor_module.Balance, "from_rao") # Call @@ -4674,9 +4617,7 @@ def test_get_crowdloan_by_id(mocker, subtensor, query_return, expected_result): fake_crowdloan_id = mocker.Mock(spec=int) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_query_return = ( - None if query_return is None else mocker.Mock(value=query_return) - ) + mocked_query_return = mocker.Mock(value=query_return) mocked_query = mocker.patch.object( subtensor.substrate, "query", return_value=mocked_query_return ) @@ -4728,13 +4669,17 @@ def test_get_crowdloans(mocker, subtensor): """Tests subtensor `get_crowdloans` method.""" # Preps fake_id = mocker.Mock(spec=int) - fake_crowdloan = mocker.Mock(value=mocker.Mock(spec=dict)) + fake_crowdloan = mocker.Mock(spec=dict) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + records = [(fake_id, fake_crowdloan)] + fake_result = mocker.MagicMock(autospec=list) + fake_result.records = records + fake_result.__iter__.return_value = iter(records) mocked_query_map = mocker.patch.object( subtensor.substrate, "query_map", - return_value=mocker.Mock(records=[(fake_id, fake_crowdloan)]), + return_value=fake_result, ) mocked_decode_crowdloan_entry = mocker.patch.object( subtensor, "_decode_crowdloan_entry" @@ -4752,7 +4697,7 @@ def test_get_crowdloans(mocker, subtensor): ) mocked_decode_crowdloan_entry.assert_called_once_with( crowdloan_id=fake_id, - data=fake_crowdloan.value, + data=fake_crowdloan, block_hash=mocked_determine_block_hash.return_value, ) assert result == [mocked_decode_crowdloan_entry.return_value] @@ -4807,23 +4752,14 @@ def test_commit_weights_with_zero_max_attempts( @pytest.mark.parametrize( "fake_result, expected_result", [ - ({"Swap": ()}, "Swap"), - ({"Keep": ()}, "Keep"), + ("Swap", "Swap"), + ("Keep", "Keep"), ( - { - "KeepSubnets": { - "subnets": ( - ( - 2, - 3, - ), - ) - } - }, + {"KeepSubnets": {"subnets": [2, 3]}}, {"KeepSubnets": {"subnets": [2, 3]}}, ), ( - {"KeepSubnets": {"subnets": ((2,),)}}, + {"KeepSubnets": {"subnets": [2]}}, { "KeepSubnets": { "subnets": [ @@ -4834,13 +4770,16 @@ def test_commit_weights_with_zero_max_attempts( ), ], ) -def test_get_root_claim_type(mocker, subtensor, fake_result, expected_result): +def test_get_root_claim_type( + mocker, subtensor, scale_type, fake_result, expected_result +): """Tests that `get_root_claim_type` calls proper methods and returns the correct value.""" # Preps fake_coldkey_ss58 = mocker.Mock(spec=str) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + scale_type.value = fake_result mocked_map = mocker.patch.object( - subtensor.substrate, "query", return_value=fake_result + subtensor.substrate, "query", return_value=scale_type ) # call @@ -4889,7 +4828,7 @@ def test_get_root_claimable_all_rates(mocker, subtensor): # Preps hotkey_ss58 = mocker.Mock(spec=str) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - fake_value = [((14, {"bits": 6520190}),)] + fake_value = [(14, {"bits": 6520190})] fake_result = mocker.MagicMock(value=fake_value) fake_result.__iter__ = fake_value mocked_query = mocker.patch.object( @@ -5229,7 +5168,7 @@ def test_get_proxy_announcement(subtensor, mocker): params=[fake_delegate_account_ss58], block_hash=mocked_determine_block_hash.return_value, ) - mocked_from_dict.assert_called_once_with(mocked_query.return_value.value[0]) + mocked_from_dict.assert_called_once_with(mocked_query.return_value.value) assert result == mocked_from_dict.return_value @@ -5833,13 +5772,15 @@ def test_get_mev_shield_current_key_success(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + fake_public_key_bytes = bytearray(b"\x00" * 1184) # ML-KEM-768 public key size mocked_determine_block_hash = mocker.patch.object( subtensor, "determine_block_hash", return_value=fake_block_hash ) mocked_query = mocker.patch.object(subtensor.substrate, "query") - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query.return_value = mocker.Mock( + spec=ScaleType, value_object=fake_public_key_bytes + ) # Call result = subtensor.get_mev_shield_current_key(block=fake_block) @@ -5863,7 +5804,11 @@ def test_get_mev_shield_current_key_none(subtensor, mocker): mocked_determine_block_hash = mocker.patch.object( subtensor, "determine_block_hash", return_value=fake_block_hash ) - mocked_query = mocker.patch.object(subtensor.substrate, "query", return_value=None) + mocked_query = mocker.patch.object( + subtensor.substrate, + "query", + return_value=mocker.Mock(spec=ScaleType, value_object=None), + ) # Call result = subtensor.get_mev_shield_current_key(block=fake_block) @@ -5883,13 +5828,15 @@ def test_get_mev_shield_current_key_invalid_size(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1000 # Invalid size + fake_public_key_bytes = bytearray(b"\x00" * 1000) # Invalid size mocked_determine_block_hash = mocker.patch.object( subtensor, "determine_block_hash", return_value=fake_block_hash ) mocked_query = mocker.patch.object(subtensor.substrate, "query") - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query.return_value = mocker.Mock( + spec=ScaleType, value_object=fake_public_key_bytes + ) # Call & Assert with pytest.raises(ValueError, match="Invalid ML-KEM-768 public key size"): @@ -5909,13 +5856,15 @@ def test_get_mev_shield_next_key_success(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1184 # ML-KEM-768 public key size + fake_public_key_bytes = bytearray(b"\x00" * 1184) # ML-KEM-768 public key size mocked_determine_block_hash = mocker.patch.object( subtensor, "determine_block_hash", return_value=fake_block_hash ) mocked_query = mocker.patch.object(subtensor.substrate, "query") - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query.return_value = mocker.Mock( + spec=ScaleType, value_object=fake_public_key_bytes + ) # Call result = subtensor.get_mev_shield_next_key(block=fake_block) @@ -5939,7 +5888,11 @@ def test_get_mev_shield_next_key_none(subtensor, mocker): mocked_determine_block_hash = mocker.patch.object( subtensor, "determine_block_hash", return_value=fake_block_hash ) - mocked_query = mocker.patch.object(subtensor.substrate, "query", return_value=None) + mocked_query = mocker.patch.object( + subtensor.substrate, + "query", + return_value=mocker.Mock(spec=ScaleType, value_object=None), + ) # Call result = subtensor.get_mev_shield_next_key(block=fake_block) @@ -5959,13 +5912,15 @@ def test_get_mev_shield_next_key_invalid_size(subtensor, mocker): # Prep fake_block = 123 fake_block_hash = "0x123abc" - fake_public_key_bytes = b"\x00" * 1000 # Invalid size + fake_public_key_bytes = bytearray(b"\x00" * 1000) # Invalid size mocked_determine_block_hash = mocker.patch.object( subtensor, "determine_block_hash", return_value=fake_block_hash ) mocked_query = mocker.patch.object(subtensor.substrate, "query") - mocked_query.return_value = iter([fake_public_key_bytes]) + mocked_query.return_value = mocker.Mock( + spec=ScaleType, value_object=fake_public_key_bytes + ) # Call & Assert with pytest.raises(ValueError, match="Invalid ML-KEM-768 public key size"): @@ -6084,7 +6039,7 @@ def test_get_start_call_delay(subtensor, mocker): # Asserts mocked_query_subtensor.assert_called_once_with(name="StartCallDelay", block=None) - assert result == mocked_query_subtensor.return_value + assert result == mocked_query_subtensor.return_value.value def test_get_coldkey_swap_announcement(subtensor, mocker): From ecfb7426b31760ec8bd6b1198946da70b0880334 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 21:10:11 +0200 Subject: [PATCH 22/38] Bump mypy --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 689cc09eff..3eade8f88f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dev = [ "pytest-cov==4.0.0", "ddt==1.6.0", "hypothesis==6.81.1", - "mypy==1.8.0", + "mypy==1.20.1", "types-retry==0.9.9.4", "typing_extensions>= 4.0.0; python_version<'3.11'", "freezegun==1.5.0", From c240424e82a0102f7553dda7fe59a6076cc2185d Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 21:15:59 +0200 Subject: [PATCH 23/38] Almost all integration tests working --- bittensor/core/chain_data/metagraph_info.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index 989bfd6861..31107310d6 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -10,6 +10,7 @@ get_netuid_and_mechid_by_storage_index, u64_normalized_float as u64tf, u16_normalized_float as u16tf, + deprecated_message, ) from bittensor.utils.balance import Balance, fixed_to_float @@ -50,6 +51,9 @@ def process_nested( data: Union[tuple, dict], chr_transform ) -> Optional[Union[list, dict]]: """Processes nested data structures by applying a transformation function to their elements.""" + deprecated_message( + "This function is deprecated as it is no longer needed with the new decoding." + ) if isinstance(data, (list, tuple)): if len(data) > 0: return [ @@ -192,11 +196,6 @@ def _from_dict(cls, decoded: dict) -> "MetagraphInfo": if decoded.get("identities") is not None: ii_list.append("identities") - for key in ii_list: - raw_data = decoded.get(key) - processed = process_nested(raw_data, _chr_str) - decoded.update({key: processed}) - return cls( # Subnet index netuid=_netuid, From 29222027aa3a8eed7ca1ec5530e3a04682d905c0 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 21:38:27 +0200 Subject: [PATCH 24/38] Some Test fixes --- bittensor/core/async_subtensor.py | 5 ++--- bittensor/core/chain_data/proxy.py | 2 +- bittensor/core/subtensor.py | 3 +-- tests/e2e_tests/test_metagraph.py | 24 ++++++++++++------------ 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ff0f1bec8a..6108303727 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1180,8 +1180,7 @@ async def bonds( uid: int bond: list[tuple[int, int]] async for uid, bond in b_map_encoded: - if len(bond) != 0: - bond_map.append((uid, bond)) + bond_map.append((uid, bond)) return bond_map @@ -4622,7 +4621,7 @@ async def get_subnet_price( params=[netuid], block_hash=block_hash, ) - price_rao = call.value + price_rao = call return Balance.from_rao(price_rao) async def get_subnet_prices( diff --git a/bittensor/core/chain_data/proxy.py b/bittensor/core/chain_data/proxy.py index 465e6b9dfd..662aad1e6b 100644 --- a/bittensor/core/chain_data/proxy.py +++ b/bittensor/core/chain_data/proxy.py @@ -238,7 +238,7 @@ def from_query(cls, query: Any) -> tuple[list["ProxyInfo"], Balance]: See: """ # proxies data is always in that path - proxies = query.value[0][0] + proxies = query.value[0] # balance data is always in that path balance = query.value[1] return cls.from_tuple(proxies), Balance.from_rao(balance) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index d3ae97eb45..0394f0cce7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -960,8 +960,7 @@ def bonds( uid: int bond: list[tuple[int, int]] for uid, bond in b_map_encoded: - if len(bond) != 0: - bond_map.append((uid, bond)) + bond_map.append((uid, bond)) return bond_map diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 6606ec62c5..274f8672ea 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -1137,8 +1137,8 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, - active=(True,), - axons=( + active=[True], + axons=[ { "block": 0, "ip": 0, @@ -1149,7 +1149,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): "protocol": 0, "version": 0, }, - ), + ], symbol=None, identity=None, network_registered_at=None, @@ -1240,8 +1240,8 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, - active=(True, True), - axons=( + active=[True, True], + axons=[ { "block": 0, "ip": 0, @@ -1262,7 +1262,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): "protocol": 0, "version": 0, }, - ), + ], symbol=None, identity=None, network_registered_at=None, @@ -1366,8 +1366,8 @@ async def test_metagraph_info_with_indexes_async( name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, - active=(True,), - axons=( + active=[True], + axons=[ { "block": 0, "ip": 0, @@ -1378,7 +1378,7 @@ async def test_metagraph_info_with_indexes_async( "protocol": 0, "version": 0, }, - ), + ], symbol=None, identity=None, network_registered_at=None, @@ -1471,8 +1471,8 @@ async def test_metagraph_info_with_indexes_async( name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, - active=(True, True), - axons=( + active=[True, True], + axons=[ { "block": 0, "ip": 0, @@ -1493,7 +1493,7 @@ async def test_metagraph_info_with_indexes_async( "protocol": 0, "version": 0, }, - ), + ], symbol=None, identity=None, network_registered_at=None, From 7cc31a67fd9675d07ec0a1e5e9a38e90482fb457 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 21:43:09 +0200 Subject: [PATCH 25/38] Some Test fixes --- tests/unit_tests/test_async_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index fbd2ba0cb3..0ee02b727a 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3686,7 +3686,7 @@ async def test_get_subnet_price(subtensor, mocker): fake_price = 29258617 expected_price = Balance.from_tao(0.029258617) mocked_query = mocker.patch.object( - subtensor.substrate, "runtime_call", return_value=mocker.Mock(value=fake_price) + subtensor.substrate, "runtime_call", return_value=fake_price ) # Call From c37667373b036d3f4299af1d85fe95e7bfac7269 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 21:52:01 +0200 Subject: [PATCH 26/38] Metagraph e2e test fix --- tests/e2e_tests/test_metagraph.py | 48 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 274f8672ea..0247f11d7d 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -671,7 +671,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): hotkeys=["5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"], coldkeys=["5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"], identities=[None], - axons=( + axons=[ { "block": 0, "version": 0, @@ -682,18 +682,18 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): "placeholder1": 0, "placeholder2": 0, }, - ), - active=(True,), - validator_permit=(False,), + ], + active=[True], + validator_permit=[False], pruning_score=[], - last_update=(0,), + last_update=[0], emission=[Balance(0).set_unit(1)], dividends=[0.0], incentives=[0.0], consensus=[0.0], trust=[], rank=[], - block_at_registration=(0,), + block_at_registration=[0], alpha_stake=[Balance.from_tao(1.0).set_unit(1)], tao_stake=[Balance(0)], total_stake=[Balance.from_tao(1.0).set_unit(1)], @@ -768,19 +768,19 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): bonds_moving_avg=4.87890977618477e-14, hotkeys=[], coldkeys=[], - identities={}, - axons=(), - active=(), - validator_permit=(), + identities=[], + axons=[], + active=[], + validator_permit=[], pruning_score=[], - last_update=(), + last_update=[], emission=[], dividends=[], incentives=[], consensus=[], trust=[], rank=[], - block_at_registration=(), + block_at_registration=[], alpha_stake=[], tao_stake=[], total_stake=[], @@ -922,7 +922,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): hotkeys=["5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"], coldkeys=["5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"], identities=[None], - axons=( + axons=[ { "block": 0, "version": 0, @@ -933,18 +933,18 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): "placeholder1": 0, "placeholder2": 0, }, - ), - active=(True,), - validator_permit=(False,), + ], + active=[True], + validator_permit=[False], pruning_score=[], - last_update=(0,), + last_update=[0], emission=[Balance(0).set_unit(1)], dividends=[0.0], incentives=[0.0], consensus=[0.0], trust=[], rank=[], - block_at_registration=(0,), + block_at_registration=[0], alpha_stake=[Balance.from_tao(1.0).set_unit(1)], tao_stake=[Balance(0)], total_stake=[Balance.from_tao(1.0).set_unit(1)], @@ -1019,19 +1019,19 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): bonds_moving_avg=4.87890977618477e-14, hotkeys=[], coldkeys=[], - identities={}, - axons=(), - active=(), - validator_permit=(), + identities=[], + axons=[], + active=[], + validator_permit=[], pruning_score=[], - last_update=(), + last_update=[], emission=[], dividends=[], incentives=[], consensus=[], trust=[], rank=[], - block_at_registration=(), + block_at_registration=[], alpha_stake=[], tao_stake=[], total_stake=[], From eebb35296f32d52e2a47f0f22217a4b7744557c0 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Wed, 15 Apr 2026 22:05:24 +0200 Subject: [PATCH 27/38] Test fix --- bittensor/core/async_subtensor.py | 29 +++++++++++++++++------------ bittensor/core/subtensor.py | 4 ++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 6108303727..ba5d16b63d 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5653,20 +5653,25 @@ async def subnet( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - query = await self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_dynamic_info", - params=[netuid], - block_hash=block_hash, - ) - price = await self.get_subnet_price( - netuid=netuid, - block=block, - block_hash=block_hash, - reuse_block=reuse_block, + decoded: Optional[dict] + price: Optional[Balance] + decoded, price = await asyncio.gather( + self.substrate.runtime_call( + "SubnetInfoRuntimeApi", + "get_dynamic_info", + params=[netuid], + block_hash=block_hash, + ), + self.get_subnet_price( + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ), + return_exceptions=True, ) - if isinstance(decoded := query.decode(), dict): + if isinstance(decoded, dict): if isinstance(price, (SubstrateRequestException, ValueError)): price = None return DynamicInfo.from_dict({**decoded, "price": price}) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0394f0cce7..0d78e94f8f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4598,14 +4598,14 @@ def subnet(self, netuid: int, block: Optional[int] = None) -> Optional[DynamicIn """ block_hash = self.determine_block_hash(block=block) - query = self.substrate.runtime_call( + decoded: Optional[dict] = self.substrate.runtime_call( api="SubnetInfoRuntimeApi", method="get_dynamic_info", params=[netuid], block_hash=block_hash, ) - if isinstance(decoded := query.decode(), dict): + if isinstance(decoded, dict): try: price = self.get_subnet_price(netuid=netuid, block=block) except (SubstrateRequestException, ValueError): From cb95640d68dcee0c26eea01791eb1c60b83f45c3 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 12:59:28 +0200 Subject: [PATCH 28/38] Dendrite Test fix --- bittensor/core/subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0d78e94f8f..23a1378dce 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3799,7 +3799,7 @@ def get_subnet_price( method="current_alpha_price", params=[netuid], block_hash=block_hash, - ).value + ) return Balance.from_rao(price_rao) def get_subnet_prices( From 6f893ab6ddb0e0568e14d631c00b701cf7ec295a Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 14:30:34 +0200 Subject: [PATCH 29/38] Metagraph Test fix --- bittensor/core/async_subtensor.py | 4 ++-- bittensor/core/subtensor.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ba5d16b63d..42d4b1385b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1491,10 +1491,10 @@ async def get_all_metagraphs_info( method=method, block_hash=block_hash, ) - if query is None or not hasattr(query, "value"): + if query is None: return None - return MetagraphInfo.list_from_dicts(query.value) + return MetagraphInfo.list_from_dicts(query) async def get_all_neuron_certificates( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 23a1378dce..9560acf503 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1189,10 +1189,10 @@ def get_all_metagraphs_info( method=method, block_hash=block_hash, ) - if query is None or not hasattr(query, "value"): + if query is None: return None - return MetagraphInfo.list_from_dicts(query.value) + return MetagraphInfo.list_from_dicts(query) def get_all_neuron_certificates( self, netuid: int, block: Optional[int] = None From 45ea74bc8adbd2384bd00449cb25bf4de26c7821 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 15:07:42 +0200 Subject: [PATCH 30/38] Unit test fixes --- tests/unit_tests/test_async_subtensor.py | 3 +-- tests/unit_tests/test_subtensor.py | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 0ee02b727a..6ab924869d 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3786,8 +3786,7 @@ async def test_subnet(subtensor, mocker): mocked_get_subnet_price = mocker.patch.object( subtensor, "get_subnet_price", return_value=Balance.from_tao(100.0) ) - mocked_decode = mocker.Mock(return_value={"netuid": netuid}) - mocked_runtime_call = mocker.Mock(decode=mocked_decode) + mocked_runtime_call = {"netuid": netuid} mocker.patch.object( subtensor.substrate, "runtime_call", return_value=mocked_runtime_call ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 94efccdc0d..d08f4c41fa 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3848,7 +3848,7 @@ def test_get_subnet_price(subtensor, mocker): fake_price = 29258617 expected_price = Balance.from_tao(0.029258617) mocked_query = mocker.patch.object( - subtensor.substrate, "runtime_call", return_value=mocker.Mock(value=fake_price) + subtensor.substrate, "runtime_call", return_value=fake_price ) # Call @@ -3940,8 +3940,7 @@ def test_subnet(subtensor, mocker): mocked_get_subnet_price = mocker.patch.object( subtensor, "get_subnet_price", return_value=Balance.from_tao(100.0) ) - mocked_decode = mocker.Mock(return_value={"netuid": netuid}) - mocked_runtime_call = mocker.Mock(decode=mocked_decode) + mocked_runtime_call = {"netuid": netuid} mocker.patch.object( subtensor.substrate, "runtime_call", return_value=mocked_runtime_call ) From 11896d4885fc37f379de651e5fb82c749de789a0 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 15:10:25 +0200 Subject: [PATCH 31/38] Neuron certificate fix --- bittensor/utils/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 6a2197132d..374f1aa67b 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -96,8 +96,7 @@ class Certificate(str): def __new__(cls, data: "str | NeuronCertificateResponse"): if isinstance(data, dict): pubkey: str = data["public_key"] - pubkey_bytes = bytes.fromhex(pubkey.removeprefix("0x")) - string = chr(data["algorithm"]) + "".join([chr(i) for i in pubkey_bytes]) + string = chr(data["algorithm"]) + pubkey else: string = data return str.__new__(cls, string) From 9b1886bd900aa59de6fb6cbfe4aeae63a7fb74a9 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 15:14:18 +0200 Subject: [PATCH 32/38] Revealed commitment fixes --- bittensor/core/async_subtensor.py | 6 ++++-- bittensor/core/subtensor.py | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 42d4b1385b..33609d1b81 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3869,9 +3869,11 @@ async def get_revealed_commitment_by_hotkey( block_hash=block_hash, reuse_block=reuse_block, ) - if query.value is None: + if query.value_serialized is None: return None - return tuple(decode_revealed_commitment(pair) for pair in query) + return tuple( + decode_revealed_commitment(pair) for pair in query.value_serialized + ) async def get_root_claim_type( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 9560acf503..ab4f82cecf 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3223,9 +3223,11 @@ def get_revealed_commitment_by_hotkey( params=[netuid, hotkey_ss58], block=block, ) - if query.value is None: + if query.value_serialized is None: return None - return tuple(decode_revealed_commitment(pair) for pair in query) + return tuple( + decode_revealed_commitment(pair) for pair in query.value_serialized + ) def get_root_claim_type( self, From afede701820a27c5bad45f1376a49f633f4ddc4c Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 15:17:00 +0200 Subject: [PATCH 33/38] subnet identity set fix --- bittensor/core/chain_data/dynamic_info.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index b92f9fa816..92373ecf1b 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -76,18 +76,15 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo": if subnet_identity := decoded.get("subnet_identity"): # we need to check it for keep backwards compatibility - logo_bytes = subnet_identity.get("logo_url") - si_logo_url = bytes(logo_bytes).decode() if logo_bytes else None - subnet_identity = SubnetIdentity( - subnet_name=bytes(subnet_identity["subnet_name"]).decode(), - github_repo=bytes(subnet_identity["github_repo"]).decode(), - subnet_contact=bytes(subnet_identity["subnet_contact"]).decode(), - subnet_url=bytes(subnet_identity["subnet_url"]).decode(), - logo_url=si_logo_url, - discord=bytes(subnet_identity["discord"]).decode(), - description=bytes(subnet_identity["description"]).decode(), - additional=bytes(subnet_identity["additional"]).decode(), + subnet_name=subnet_identity["subnet_name"], + github_repo=subnet_identity["github_repo"], + subnet_contact=subnet_identity["subnet_contact"], + subnet_url=subnet_identity["subnet_url"], + logo_url=subnet_identity.get("logo_url", ""), + discord=subnet_identity["discord"], + description=subnet_identity["description"], + additional=subnet_identity["additional"], ) else: subnet_identity = None From bca99f9c5da579f444de131c287e2b800d168f07 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 15:28:11 +0200 Subject: [PATCH 34/38] Bump cyscale --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 424b32d763..73425fa330 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ "retry==0.9.2", "requests>=2.0.0,<3.0", "pydantic>=2.3,<3", - "cyscale==0.2.1", + "cyscale==0.2.2", "uvicorn", "bittensor-drand>=1.3.0,<2.0.0", "bittensor-wallet==4.0.1", From 84a0be3175dfff8608774be4d345d2a7bc9fd674 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 15:28:28 +0200 Subject: [PATCH 35/38] Bump reqs to safe versions --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 73425fa330..b75919f75c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ requires-python = ">=3.10,<3.15" dependencies = [ "wheel", "setuptools~=70.0", - "aiohttp>=3.9,<4.0", + "aiohttp>=3.13.4,<4.0", "asyncstdlib~=3.13.0", "colorama~=0.4.6", "fastapi>=0.110.1", @@ -27,7 +27,7 @@ dependencies = [ "pycryptodome>=3.18.0,<4.0.0", "pyyaml>=6.0", "retry==0.9.2", - "requests>=2.0.0,<3.0", + "requests>=2.33.0,<3.0", "pydantic>=2.3,<3", "cyscale==0.2.2", "uvicorn", From d79e81e9eca00c3641026b7e85a3bd73d62d5b1d Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 17:22:48 +0200 Subject: [PATCH 36/38] Type fixes --- bittensor/core/async_subtensor.py | 21 ++++++---- bittensor/core/subtensor.py | 70 ++++++++++++++++++------------- bittensor/core/types.py | 38 ++++++++++++++++- 3 files changed, 91 insertions(+), 38 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 33609d1b81..ec49f9461b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -11,7 +11,7 @@ from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT -from scalecodec import GenericCall +from scalecodec import GenericCall, ScaleValue from scalecodec.base import ScaleType from scalecodec.utils.math import FixedPoint, fixed_to_decimal @@ -151,6 +151,7 @@ NeuronCertificateResponse, CommitmentOfResponse, CrowdloansResponse, + DynamicInfoResponse, ) from bittensor.utils import ( Certificate, @@ -528,7 +529,7 @@ async def _query_with_fallback( *args: tuple[str, str, Optional[list[Any]]], block_hash: Optional[str] = None, default_value: Any = ValueError, - ) -> ScaleType[Any] | Any: + ) -> ScaleType[ScaleValue] | Any: """ Queries the subtensor node with a given set of args, falling back to the next group if the method does not exist at the given block. This method exists to support backwards compatibility for blocks. @@ -753,7 +754,7 @@ async def query_constant( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[ScaleType[Any]]: + ) -> Optional[ScaleType[ScaleValue]]: """Retrieves a constant from the specified module on the Bittensor blockchain. Use this function for nonstandard queries to constants defined within the Bittensor blockchain, if these cannot @@ -854,7 +855,7 @@ async def query_module( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> ScaleType[Any]: + ) -> ScaleType[ScaleValue]: """Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This function is a generic query interface that allows for flexible and diverse data retrieval from various blockchain modules. Use this function for nonstandard queries to storage defined within the Bittensor @@ -922,7 +923,7 @@ async def query_subtensor( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[ScaleType[Any]]: + ) -> ScaleType[ScaleValue]: """Queries named storage from the Subtensor module on the Bittensor blockchain. Use this function for nonstandard queries to storage defined within the Bittensor blockchain, if these cannot @@ -1008,7 +1009,7 @@ async def all_subnets( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - decoded: list[dict[str, Any]] + decoded: list[DynamicInfoResponse] subnet_prices: dict[int, Balance] decoded, subnet_prices = await asyncio.gather( @@ -1728,7 +1729,9 @@ async def get_balances( ) for address in addresses ] - batch_call = await self.substrate.query_multi(calls, block_hash=block_hash) + batch_call: list[tuple[StorageKey, dict]] = await self.substrate.query_multi( + calls, block_hash=block_hash + ) # type: ignore[assignment] results = {} for item in batch_call: value = item[1] or {"data": {"free": 0}} @@ -3693,7 +3696,7 @@ async def get_proxy_announcement( - See: """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query: ScaleType[tuple[list[dict], int]] = await self.substrate.query( + query: ScaleType[tuple[list[dict], int]] = await self.substrate.query( # type: ignore[assignment] module="Proxy", storage_function="Announcements", params=[delegate_account_ss58], @@ -5655,7 +5658,7 @@ async def subnet( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - decoded: Optional[dict] + decoded: Optional[DynamicInfoResponse] price: Optional[Balance] decoded, price = await asyncio.gather( self.substrate.runtime_call( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ab4f82cecf..3c748b922b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -10,6 +10,7 @@ from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT +from scalecodec import ScaleValue from scalecodec.base import ScaleType from scalecodec.utils.math import FixedPoint, fixed_to_decimal @@ -150,6 +151,7 @@ NeuronCertificateResponse, CommitmentOfResponse, CrowdloansResponse, + DynamicInfoResponse, ) from bittensor.utils import ( Certificate, @@ -636,7 +638,7 @@ def sim_swap( def query_constant( self, module_name: str, constant_name: str, block: Optional[int] = None - ) -> Optional[ScaleType[Any]]: + ) -> Optional[ScaleType[ScaleValue]]: """Retrieves a constant from the specified module on the Bittensor blockchain. Use this function for nonstandard queries to constants defined within the Bittensor blockchain, if these cannot @@ -773,7 +775,7 @@ def query_subtensor( name: str, params: Optional[list] = None, block: Optional[int] = None, - ) -> ScaleType[Any]: + ) -> ScaleType[ScaleValue]: """Queries named storage from the Subtensor module on the Bittensor blockchain. Use this function for nonstandard queries to constants defined within the Bittensor blockchain, if these cannot @@ -832,10 +834,13 @@ def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo a subnet, or None if the query fails. """ block_hash = self.determine_block_hash(block=block) - decoded = self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_all_dynamic_info", - block_hash=block_hash, + decoded = cast( + list[DynamicInfoResponse], + self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", + block_hash=block_hash, + ), ) try: subnet_prices = self.get_subnet_prices(block=block) @@ -864,7 +869,7 @@ def blocks_since_last_step( Notes: - """ - query: ScaleType[int] = self.query_subtensor( + query: ScaleType[int] = self.query_subtensor( # type: ignore[assignment] name="BlocksSinceLastStep", block=block, params=[netuid] ) return query.value @@ -1191,7 +1196,7 @@ def get_all_metagraphs_info( ) if query is None: return None - + assert isinstance(query, list) return MetagraphInfo.list_from_dicts(query) def get_all_neuron_certificates( @@ -1377,7 +1382,9 @@ def get_balances( ) for address in addresses ] - batch_call = self.substrate.query_multi(calls, block_hash=block_hash) + batch_call: list[tuple[StorageKey, dict]] = self.substrate.query_multi( # type: ignore[assignment] + calls, block_hash=block_hash + ) results = {} key: StorageKey val: dict @@ -1552,8 +1559,7 @@ def get_children_pending( block_hash=self.determine_block_hash(block), ) children, cooldown = cast( - tuple[list[tuple[int, Any]], int], - getattr(pending_query, "value", pending_query), + tuple[list[tuple[int, Any]], int], pending_query.value ) return ( @@ -2125,7 +2131,7 @@ def get_delegate_take(self, hotkey_ss58: str, block: Optional[int] = None) -> fl Notes: - """ - result: ScaleType[int] = self.query_subtensor( + result: ScaleType[int] = self.query_subtensor( # type: ignore[assignment] name="Delegates", block=block, params=[hotkey_ss58], @@ -2210,7 +2216,7 @@ def get_existential_deposit(self, block: Optional[int] = None) -> Balance: Notes: - """ - result: Optional[ScaleType[int]] = self.substrate.get_constant( + result: Optional[ScaleType[int]] = self.substrate.get_constant( # type: ignore[assignment] module_name="Balances", constant_name="ExistentialDeposit", block_hash=self.determine_block_hash(block), @@ -2417,9 +2423,12 @@ def get_liquidity_list( ) ) - fee_global_tao = fixed_to_float(fee_global_tao_query[1]) - fee_global_alpha = fixed_to_float(fee_global_alpha_query[1]) - sqrt_price = fixed_to_float(sqrt_price_query[1]) + fee_global_tao_raw: FixedPoint = fee_global_tao_query[1] # type: ignore[assignment] + fee_global_alpha_raw: FixedPoint = fee_global_alpha_query[1] # type: ignore[assignment] + sqrt_price_raw: FixedPoint = sqrt_price_query[1] # type: ignore[assignment] + fee_global_tao = fixed_to_float(fee_global_tao_raw) + fee_global_alpha = fixed_to_float(fee_global_alpha_raw) + sqrt_price = fixed_to_float(sqrt_price_raw) current_tick = price_to_tick(sqrt_price**2) positions_values: list[tuple[PositionResponse, int, int]] = [] @@ -2448,7 +2457,8 @@ def get_liquidity_list( positions_storage_keys, block_hash=block_hash ) # iterator with just the values - ticks = iter([x[1] for x in ticks_query]) + tick_values: list[dict] = [x[1] for x in ticks_query] # type: ignore + ticks = iter(tick_values) positions: list[LiquidityPosition] = [] for position, tick_low_idx, tick_high_idx in positions_values: tick_low = next(ticks) @@ -3557,7 +3567,7 @@ def get_stake_for_hotkey( netuid: The subnet ID to query for. block: The block number at which to query the stake information. """ - hotkey_alpha_query = self.query_subtensor( + hotkey_alpha_query: ScaleType[int] = self.query_subtensor( # type: ignore[assignment] name="TotalHotkeyAlpha", params=[hotkey_ss58, netuid], block=block ) return Balance.from_rao(hotkey_alpha_query.value, netuid=netuid) @@ -3666,10 +3676,11 @@ def get_start_call_delay(self, block: Optional[int] = None) -> int: Return: Amount of blocks after the start call can be executed. """ - return self.query_subtensor( + query: ScaleType[int] = self.query_subtensor( # type: ignore[assignment] name="StartCallDelay", block=block, - ).value + ) + return query.value def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[Balance]: """ @@ -3796,7 +3807,7 @@ def get_subnet_price( return Balance.from_tao(1) block_hash = self.determine_block_hash(block=block) - price_rao = self.substrate.runtime_call( + price_rao: int = self.substrate.runtime_call( # type: ignore[assignment] api="SwapRuntimeApi", method="current_alpha_price", params=[netuid], @@ -3924,7 +3935,7 @@ def get_timelocked_weight_commits( ) commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] + return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] # type: ignore[arg-type,union-attr] def get_timestamp(self, block: Optional[int] = None) -> datetime: """ @@ -4332,7 +4343,7 @@ def is_subnet_active(self, netuid: int, block: Optional[int] = None) -> bool: block=block, params=[netuid], ) - qv: Optional[int] = query.value + qv: Optional[int] = query.value # type: ignore[assignment] if qv is None or qv <= 0: return False else: @@ -4600,11 +4611,14 @@ def subnet(self, netuid: int, block: Optional[int] = None) -> Optional[DynamicIn """ block_hash = self.determine_block_hash(block=block) - decoded: Optional[dict] = self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_dynamic_info", - params=[netuid], - block_hash=block_hash, + decoded = cast( + Optional[DynamicInfoResponse], + self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_dynamic_info", + params=[netuid], + block_hash=block_hash, + ), ) if isinstance(decoded, dict): diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 18d9a9cb9f..238aa1d269 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,7 +1,7 @@ import argparse from abc import ABC from dataclasses import dataclass -from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING +from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING, NotRequired import numpy as np from numpy.typing import NDArray @@ -616,3 +616,39 @@ class CrowdloansResponse(TypedDict): call: Optional[dict] finalized: bool contributors_count: int + + +class SubnetIdentityResponse(TypedDict): + subnet_name: str + github_repo: str + subnet_contact: str + subnet_url: str + discord: str + description: str + logo_url: str + additional: str + + +class DynamicInfoResponse(TypedDict): + netuid: int + owner_hotkey: str + owner_coldkey: str + subnet_name: list[int] # needs bytes.decode('utf-8') to stringify + token_symbol: list[int] # needs bytes.decode('utf-8') to stringify + tempo: int + last_step: int + blocks_since_last_step: int + emission: int + alpha_in: int + alpha_out: int + tao_in: int + alpha_out_emission: int + alpha_in_emission: int + tao_in_emission: int + pending_alpha_emission: int + pending_root_emission: int + subnet_volume: int + network_registered_at: int + subnet_identity: SubnetIdentityResponse + moving_price: FixedPoint + price: NotRequired[Balance] From bf7a7301874d1fe86ea0b9daa37896d27bde16cd Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 17:26:26 +0200 Subject: [PATCH 37/38] Import fixes --- bittensor/core/types.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 238aa1d269..67ce355cf4 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,7 +1,7 @@ import argparse from abc import ABC from dataclasses import dataclass -from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING, NotRequired +from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING import numpy as np from numpy.typing import NDArray @@ -21,6 +21,12 @@ ) from bittensor.utils.btlogging import logging +try: + from typing import NotRequired +except ImportError: + # fallback to typing_extensions if Python < 3.11 + from typing_extensions import NotRequired + if TYPE_CHECKING: from bittensor_wallet import Wallet from bittensor.utils.balance import Balance @@ -651,4 +657,4 @@ class DynamicInfoResponse(TypedDict): network_registered_at: int subnet_identity: SubnetIdentityResponse moving_price: FixedPoint - price: NotRequired[Balance] + price: NotRequired["Balance"] From ee92840ab17bfd73724a6a50aceab046be49c2db Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 16 Apr 2026 19:11:16 +0200 Subject: [PATCH 38/38] Fixes _decode_crowdloan_entry and adds a test --- bittensor/core/async_subtensor.py | 13 +- bittensor/core/chain_data/crowdloan_info.py | 2 +- bittensor/core/subtensor.py | 13 +- tests/helpers/integration_websocket_data.py | 115 ++++++++++++++++++ .../test_subtensor_integration.py | 54 +++++++- 5 files changed, 182 insertions(+), 15 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ec49f9461b..5c0a02ae69 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -360,14 +360,15 @@ async def _decode_crowdloan_entry( call_data = data.get("call") if call_data and "Inline" in call_data: try: - inline_bytes = bytes(call_data["Inline"][0][0]) - scale_object = await self.substrate.create_scale_object( - type_string="Call", - data=scalecodec.ScaleBytes(inline_bytes), + runtime = await self.substrate.init_runtime(block_hash=block_hash) + call_obj = await self.substrate.create_scale_object( + "Call", + data=scalecodec.ScaleBytes(call_data["Inline"]), block_hash=block_hash, + runtime=runtime, ) - decoded_call = scale_object.decode() - data["call"] = decoded_call + call_value = call_obj.decode() + data["call"] = call_value except Exception as e: data["call"] = {"decode_error": str(e), "raw": call_data} diff --git a/bittensor/core/chain_data/crowdloan_info.py b/bittensor/core/chain_data/crowdloan_info.py index 51951287af..701972bad2 100644 --- a/bittensor/core/chain_data/crowdloan_info.py +++ b/bittensor/core/chain_data/crowdloan_info.py @@ -40,7 +40,7 @@ class CrowdloanInfo: funds_account: str raised: Balance target_address: Optional[str] - call: Optional[str] + call: Optional[dict] finalized: bool contributors_count: int diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 3c748b922b..5313a4195c 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -303,14 +303,13 @@ def _decode_crowdloan_entry( call_data = data.get("call") if call_data and "Inline" in call_data: try: - # TODO need a working crowdloan call to see what this actually is, but I probably need to just remove this section - inline_bytes = bytes(call_data["Inline"][0][0]) - decoded_call = self.substrate.create_scale_object( - type_string="Call", - data=scalecodec.ScaleBytes(inline_bytes), + call_obj = self.substrate.create_scale_object( + "Call", + data=scalecodec.ScaleBytes(call_data["Inline"]), block_hash=block_hash, - ).decode() - data["call"] = decoded_call + ) + call_value = call_obj.decode() + data["call"] = call_value except Exception as e: data["call"] = {"decode_error": str(e), "raw": call_data} diff --git a/tests/helpers/integration_websocket_data.py b/tests/helpers/integration_websocket_data.py index 307436fd86..a83492dc03 100644 --- a/tests/helpers/integration_websocket_data.py +++ b/tests/helpers/integration_websocket_data.py @@ -12427,4 +12427,119 @@ } }, }, + "decode_crowdloan_entry": { + "chain_getHead": { + "[]": { + "result": "0x8563f9d378caf2943368320b79ab5a90ec51ffb2ba7ab23002a811819297316d" + } + }, + "chain_getHeader": { + '["0xe0abc804366b36f9293c2a00fbc7a911c19247885f39a99a20f5c06389a4093e"]': { + "result": { + "parentHash": "0xf287b6afbb10dd480373d62a6d47f2b8f7c0419cc6ac5d61ba0c0b7f189d17ba", + "number": "0x79ccc2", + "stateRoot": "0x3247e495b19698fae0064a9bc61c7723d28adbb8c6806f8605ec7bab74bde028", + "extrinsicsRoot": "0xb79368ee28d3ca69081559d11928b9c718d4a9df865e2e49861ecd9d64e67b41", + "digest": { + "logs": [ + "0x066175726120c6c1d20800000000", + "0x0466726f6e890201f2206a7ea2500ddf7de73fb26e223298f68422086014b3a76e93b43af11fd8c410a39ff5f50fa3b06912db770e3dc5345aeb4e8e1811918362aa52268c2d508411d7058ab4599ff651cc7af3e46d62305f8cc0fc7cfe9010cad57b0456df620d42858bd66a675981f36caab57b5d45466a0dcddfd0447a83231dfdf38c70a34dc6247105ed2a0dfb4c02614ce6b405fd600cfe7a3f5d4dd578a8e0f87c712fec81", + "0x0561757261010114610206165bf247c62abbec8218aadf695afe1df9894195795d510eae5a2b1241a93d5e8f8262a6bb6a4d6d1eacd408609b2d925d770adbfa47f78f7efb0985", + ] + }, + } + }, + '["0x8563f9d378caf2943368320b79ab5a90ec51ffb2ba7ab23002a811819297316d"]': { + "result": { + "parentHash": "0xe0abc804366b36f9293c2a00fbc7a911c19247885f39a99a20f5c06389a4093e", + "number": "0x79ccc3", + "stateRoot": "0xf79f46321b59ee29fdb69327bf18e3d6bb7886ffdb972ba4a862a666c4d5ced6", + "extrinsicsRoot": "0xd4b2304c575e1d6d0a1e2baf6d7a1288415121e0b30d37319d182e95358565cf", + "digest": { + "logs": [ + "0x066175726120c7c1d20800000000", + "0x0466726f6e890201a781ed2bf8f8c5a308a7c69e1a2c1668c07fa1fd6105f57df141b282f34fb6b510a2973c5c0c1949c76e622fab9f33c8a31e63a16773ae1b7917babc0fc916304483ecd3e5ba88ad18abbee85b43ab2952d3ee8823dc7357db4cb2c20f2612b4fc29e6837aa829a90e91b78a072a91208f66b06d388347cc250e1475b9470f3c71bb26aa52583dca3f6f258fe1440fdac6044c6e6e3c3971c5e3cabbc75cb86790", + "0x05617572610101e059e901febcad1547d1f1809f6f663f56e6e14e2eac77e6899387b43ade5b023f0c4fc1bc7153be4c725b6c34fae5397ac994980550638ea480b0095294f08d", + ] + }, + } + }, + }, + "state_getRuntimeVersion": { + '["0xf287b6afbb10dd480373d62a6d47f2b8f7c0419cc6ac5d61ba0c0b7f189d17ba"]': { + "result": { + "specName": "node-subtensor", + "implName": "node-subtensor", + "authoringVersion": 1, + "specVersion": 393, + "implVersion": 1, + "apis": [ + ["0xdf6acb689907609b", 5], + ["0x37e397fc7c91f5e4", 2], + ["0x40fe3ad401f8959a", 6], + ["0xfbc577b9d747efd6", 1], + ["0xd2bc9897eed08f15", 3], + ["0xf78b278be53f454c", 2], + ["0xdd718d5cc53262d4", 1], + ["0xab3c0572291feb8b", 1], + ["0xed99c5acb25eedf5", 3], + ["0xbc9d89904f5b923f", 1], + ["0x37c8bb1350a9a2a8", 4], + ["0xf3ff14d5ab527059", 3], + ["0x582211f65bb14b89", 6], + ["0xe65b00e46cedd0aa", 2], + ["0x68b66ba122c93fa7", 2], + ["0x42e62be4a39e5b60", 1], + ["0x806df4ccaa9ed485", 1], + ["0x8375104b299b74c5", 1], + ["0x5d1fbfbe852f2807", 1], + ["0xc6886e2f8e598b0a", 1], + ["0xcbca25e39f142387", 2], + ["0xa8b093e6508d9e9c", 1], + ["0x1c4585bd5c707202", 1], + ], + "transactionVersion": 1, + "systemVersion": 1, + "stateVersion": 1, + } + }, + '["0xe0abc804366b36f9293c2a00fbc7a911c19247885f39a99a20f5c06389a4093e"]': { + "result": { + "specName": "node-subtensor", + "implName": "node-subtensor", + "authoringVersion": 1, + "specVersion": 393, + "implVersion": 1, + "apis": [ + ["0xdf6acb689907609b", 5], + ["0x37e397fc7c91f5e4", 2], + ["0x40fe3ad401f8959a", 6], + ["0xfbc577b9d747efd6", 1], + ["0xd2bc9897eed08f15", 3], + ["0xf78b278be53f454c", 2], + ["0xdd718d5cc53262d4", 1], + ["0xab3c0572291feb8b", 1], + ["0xed99c5acb25eedf5", 3], + ["0xbc9d89904f5b923f", 1], + ["0x37c8bb1350a9a2a8", 4], + ["0xf3ff14d5ab527059", 3], + ["0x582211f65bb14b89", 6], + ["0xe65b00e46cedd0aa", 2], + ["0x68b66ba122c93fa7", 2], + ["0x42e62be4a39e5b60", 1], + ["0x806df4ccaa9ed485", 1], + ["0x8375104b299b74c5", 1], + ["0x5d1fbfbe852f2807", 1], + ["0xc6886e2f8e598b0a", 1], + ["0xcbca25e39f142387", 2], + ["0xa8b093e6508d9e9c", 1], + ["0x1c4585bd5c707202", 1], + ], + "transactionVersion": 1, + "systemVersion": 1, + "stateVersion": 1, + } + }, + }, + }, } diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index 527076f07b..8f3708a3b0 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -1,7 +1,8 @@ import pytest -from bittensor.core.chain_data import AxonInfo, NeuronInfo +from bittensor.core.chain_data import AxonInfo, NeuronInfo, CrowdloanInfo from bittensor.core.subtensor import Subtensor +from bittensor.core.types import CrowdloansResponse from bittensor.utils.balance import Balance from tests.helpers.helpers import FakeWebsocket from bittensor.utils.mock.subtensor_mock import MockSubtensor @@ -161,3 +162,54 @@ async def test_archive_node_retry(mocker): current_block = subtensor.substrate.get_block_number() old_block = current_block - 1000 assert isinstance((subtensor.substrate.get_block(block_number=old_block)), dict) + + +@pytest.mark.asyncio +async def test_decode_crowdloan_entry(mocker): + entry = CrowdloansResponse( + **{ + "creator": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "deposit": 10000000000, + "min_contribution": 1000000000, + "end": 10055, + "cap": 100000000000, + "funds_account": "5EYCAe5fvncWtwXjyNBBHFPvNVDH5LPQ2harKS7KdAGbezkb", + "raised": 10000000000, + "target_address": None, + "call": { + "Inline": "0x0500008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4802286bee" + }, + "finalized": False, + "contributors_count": 1, + } + ) + subtensor = await prepare_test(mocker, "decode_crowdloan_entry") + actual = subtensor._decode_crowdloan_entry(17, entry) + expected = CrowdloanInfo( + id=17, + creator="5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + deposit=Balance.from_tao(10.0), + min_contribution=Balance.from_tao(1.0), + end=10055, + cap=Balance.from_tao(100.0), + funds_account="5EYCAe5fvncWtwXjyNBBHFPvNVDH5LPQ2harKS7KdAGbezkb", + raised=Balance.from_tao(10.0), + target_address=None, + call={ + "call_index": "0x0500", + "call_function": "transfer_allow_death", + "call_module": "Balances", + "call_args": [ + { + "name": "dest", + "type": "AccountIdLookupOf", + "value": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + }, + {"name": "value", "type": "Balance", "value": 1000000000}, + ], + "call_hash": "0x117349ae93488150fa503b1ff7a0a94bfaa3ba193950a3d812ce32b9bb69fb02", + }, + finalized=False, + contributors_count=1, + ) + assert actual == expected