From 4d36b75b247e1f086eabda65f829c857cb70cafc Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 26 Nov 2024 23:41:39 +0200 Subject: [PATCH 1/2] Expands the use of reuse_block to all methods that accept block_hash as an arg --- bittensor/core/async_subtensor.py | 119 +++++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 19 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4c456ac83a..08112f411c 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -256,7 +256,10 @@ async def get_block_hash(self, block_id: Optional[int] = None): return await self.substrate.get_chain_head() async def is_hotkey_registered_any( - self, hotkey_ss58: str, block_hash: Optional[str] = None + self, + hotkey_ss58: str, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> bool: """ Checks if a neuron's hotkey is registered on any subnet within the Bittensor network. @@ -264,22 +267,27 @@ async def is_hotkey_registered_any( Args: hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. block_hash (Optional[str]): The blockchain block_hash representation of block id. + reuse_block (bool): Whether to reuse the last-used block hash. Returns: bool: ``True`` if the hotkey is registered on any subnet, False otherwise. This function is essential for determining the network-wide presence and participation of a neuron. """ - return len(await self.get_netuids_for_hotkey(hotkey_ss58, block_hash)) > 0 + return ( + len(await self.get_netuids_for_hotkey(hotkey_ss58, block_hash, reuse_block)) + > 0 + ) async def get_subnet_burn_cost( - self, block_hash: Optional[str] = None + self, block_hash: Optional[str] = None, reuse_block: bool = False ) -> Optional[str]: """ Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the amount of Tao that needs to be locked or burned to establish a new subnet. Args: block_hash (Optional[int]): The blockchain block_hash of the block id. + reuse_block (bool): Whether to reuse the last-used block hash. Returns: int: The burn cost for subnet registration. @@ -291,18 +299,20 @@ async def get_subnet_burn_cost( method="get_network_registration_cost", params=[], block_hash=block_hash, + reuse_block=reuse_block, ) return lock_cost async def get_total_subnets( - self, block_hash: Optional[str] = None + self, block_hash: Optional[str] = None, reuse_block: bool = False ) -> Optional[int]: """ Retrieves the total number of subnets within the Bittensor network as of a specific blockchain block. Args: block_hash (Optional[str]): The blockchain block_hash representation of block id. + reuse_block (bool): Whether to reuse the last-used block hash. Returns: Optional[str]: The total number of subnets in the network. @@ -314,15 +324,19 @@ async def get_total_subnets( storage_function="TotalNetworks", params=[], block_hash=block_hash, + reuse_block_hash=reuse_block, ) return result - async def get_subnets(self, block_hash: Optional[str] = None) -> list[int]: + async def get_subnets( + self, block_hash: Optional[str] = None, reuse_block: bool = False + ) -> list[int]: """ Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. Args: block_hash (Optional[str]): The hash of the block to retrieve the subnet unique identifiers from. + reuse_block (bool): Whether to reuse the last-used block hash. Returns: A list of subnet netuids. @@ -334,7 +348,7 @@ async def get_subnets(self, block_hash: Optional[str] = None) -> list[int]: module="SubtensorModule", storage_function="NetworksAdded", block_hash=block_hash, - reuse_block_hash=True, + reuse_block_hash=reuse_block, ) return ( [] @@ -426,7 +440,11 @@ async def get_stake_info_for_coldkey( return StakeInfo.list_from_vec_u8(hex_to_bytes(hex_bytes_result)) async def get_stake_for_coldkey_and_hotkey( - self, hotkey_ss58: str, coldkey_ss58: str, block_hash: Optional[str] = None + self, + hotkey_ss58: str, + coldkey_ss58: str, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Balance: """ Retrieves stake information associated with a specific coldkey and hotkey. @@ -435,6 +453,7 @@ async def get_stake_for_coldkey_and_hotkey( hotkey_ss58 (str): the hotkey SS58 address to query coldkey_ss58 (str): the coldkey SS58 address to query block_hash (Optional[str]): the hash of the blockchain block number for the query. + reuse_block (Optional[bool]): whether to reuse the last-used block hash. Returns: Stake Balance for the given coldkey and hotkey @@ -444,6 +463,7 @@ async def get_stake_for_coldkey_and_hotkey( storage_function="Stake", params=[hotkey_ss58, coldkey_ss58], block_hash=block_hash, + reuse_block_hash=reuse_block, ) return Balance.from_rao(_result or 0) @@ -508,6 +528,7 @@ async def get_balance( self, *addresses: str, block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> dict[str, Balance]: """ Retrieves the balance for given coldkey(s) @@ -515,10 +536,15 @@ async def get_balance( Args: addresses (str): coldkey addresses(s). block_hash (Optional[str]): the block hash, optional. + reuse_block (Optional[bool]): whether to reuse the last-used block hash. Returns: Dict of {address: Balance objects}. """ + if reuse_block: + block_hash = self.substrate.last_block_hash + elif not block_hash: + block_hash = await self.get_block_hash() calls = [ ( await self.substrate.create_storage_key( @@ -587,6 +613,7 @@ async def get_total_stake_for_coldkey( self, *ss58_addresses, block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> dict[str, Balance]: """ Returns the total stake held on a coldkey. @@ -594,10 +621,15 @@ async def get_total_stake_for_coldkey( Args: ss58_addresses (tuple[str]): The SS58 address(es) of the coldkey(s) block_hash (str): The hash of the block number to retrieve the stake from. + reuse_block (bool): Whether to reuse the last-used block hash. Returns: Dict in view {address: Balance objects}. """ + if reuse_block: + block_hash = self.substrate.last_block_hash + elif not block_hash: + block_hash = await self.get_block_hash() calls = [ ( await self.substrate.create_storage_key( @@ -783,7 +815,7 @@ async def get_existential_deposit( return Balance.from_rao(result) async def neurons( - self, netuid: int, block_hash: Optional[str] = None + self, netuid: int, block_hash: Optional[str] = None, reuse_block: bool = False ) -> list[NeuronInfo]: """ Retrieves a list of all neurons within a specified subnet of the Bittensor network. @@ -792,12 +824,19 @@ async def neurons( Args: netuid (int): The unique identifier of the subnet. block_hash (str): The hash of the blockchain block number for the query. + reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: A list of NeuronInfo objects detailing each neuron's characteristics in the subnet. Understanding the distribution and status of neurons within a subnet is key to comprehending the network's decentralized structure and the dynamics of its consensus and governance processes. """ + if not block_hash: + if reuse_block: + block_hash = self.substrate.last_block_hash + else: + block_hash = await self.substrate.get_chain_head() + neurons_lite, weights, bonds = await asyncio.gather( self.neurons_lite(netuid=netuid, block_hash=block_hash), self.weights(netuid=netuid, block_hash=block_hash), @@ -891,7 +930,11 @@ async def get_neuron_for_pubkey_and_subnet( return NeuronInfo.from_vec_u8(bytes(result)) async def neuron_for_uid( - self, uid: Optional[int], netuid: int, block_hash: Optional[str] = None + self, + uid: Optional[int], + netuid: int, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> NeuronInfo: """ Retrieves detailed information about a specific neuron identified by its unique identifier (UID) within a specified subnet (netuid) of the Bittensor network. This function provides a comprehensive view of a neuron's attributes, including its stake, rank, and operational status. @@ -900,6 +943,7 @@ async def neuron_for_uid( uid (int): The unique identifier of the neuron. netuid (int): The unique identifier of the subnet. block_hash (str): The hash of the blockchain block number for the query. + reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: Detailed information about the neuron if found, a null neuron otherwise @@ -909,6 +953,9 @@ async def neuron_for_uid( if uid is None: return NeuronInfo.get_null_neuron() + if reuse_block: + block_hash = self.substrate.last_block_hash + params = [netuid, uid, block_hash] if block_hash else [netuid, uid] json_body = await self.substrate.rpc_request( method="neuronInfo_getNeuron", @@ -992,7 +1039,7 @@ async def query_identity( return {} async def weights( - self, netuid: int, block_hash: Optional[str] = None + self, netuid: int, block_hash: Optional[str] = None, reuse_block: bool = False ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -1001,6 +1048,7 @@ async def weights( Args: netuid (int): The network UID of the subnet to query. block_hash (str): The hash of the blockchain block for the query. + reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: A list of tuples mapping each neuron's UID to its assigned weights. @@ -1013,13 +1061,14 @@ async def weights( storage_function="Weights", params=[netuid], block_hash=block_hash, + reuse_block_hash=reuse_block, ) w_map = [(uid, w or []) async for uid, w in w_map_encoded] return w_map async def bonds( - self, netuid: int, block_hash: Optional[str] = None + self, netuid: int, block_hash: Optional[str] = None, reuse_block: bool = False ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the bond distribution set by neurons within a specific subnet of the Bittensor network. @@ -1028,6 +1077,7 @@ async def bonds( Args: netuid (int): The network UID of the subnet to query. block_hash (Optional[str]): The hash of the blockchain block number for the query. + reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: List of tuples mapping each neuron's UID to its bonds with other neurons. @@ -1039,6 +1089,7 @@ async def bonds( storage_function="Bonds", params=[netuid], block_hash=block_hash, + reuse_block_hash=reuse_block, ) b_map = [(uid, b) async for uid, b in b_map_encoded] @@ -1077,7 +1128,10 @@ async def does_hotkey_exist( return return_val async def get_hotkey_owner( - self, hotkey_ss58: str, block_hash: str + self, + hotkey_ss58: str, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Optional[str]: """ Retrieves the owner of the given hotkey at a specific block hash. @@ -1085,7 +1139,8 @@ async def get_hotkey_owner( Args: hotkey_ss58 (str): The SS58 address of the hotkey. - block_hash (str): The hash of the block at which to check the hotkey ownership. + block_hash (Optional[str]): The hash of the block at which to check the hotkey ownership. + reuse_block (bool): Whether to reuse the last-used blockchain hash. Returns: Optional[str]: The SS58 address of the owner if the hotkey exists, or None if it doesn't. @@ -1095,6 +1150,7 @@ async def get_hotkey_owner( storage_function="Owner", params=[hotkey_ss58], block_hash=block_hash, + reuse_block_hash=reuse_block, ) val = decode_account_id(hk_owner_query[0]) if val: @@ -1176,7 +1232,7 @@ async def get_children(self, hotkey: str, netuid: int) -> tuple[bool, list, str] return False, [], format_error_message(e, self.substrate) async def get_subnet_hyperparameters( - self, netuid: int, block_hash: Optional[str] = None + self, netuid: int, block_hash: Optional[str] = None, reuse_block: bool = False ) -> Optional[Union[list, SubnetHyperparameters]]: """ Retrieves the hyperparameters for a specific subnet within the Bittensor network. These hyperparameters define the operational settings and rules governing the subnet's behavior. @@ -1184,6 +1240,7 @@ async def get_subnet_hyperparameters( Args: netuid (int): The network UID of the subnet to query. block_hash (Optional[str]): The hash of the blockchain block number for the query. + reuse_block (bool): Whether to reuse the last-used blockchain hash. Returns: The subnet's hyperparameters, or `None` if not available. @@ -1195,6 +1252,7 @@ async def get_subnet_hyperparameters( method="get_subnet_hyperparams", params=[netuid], block_hash=block_hash, + reuse_block=reuse_block, ) if hex_bytes_result is None: @@ -1234,13 +1292,14 @@ async def get_vote_data( return ProposalVoteData(vote_data) async def get_delegate_identities( - self, block_hash: Optional[str] = None + self, block_hash: Optional[str] = None, reuse_block: bool = False ) -> dict[str, DelegatesDetails]: """ Fetches delegates identities from the chain and GitHub. Preference is given to chain data, and missing info is filled-in by the info from GitHub. At some point, we want to totally move away from fetching this info from GitHub, but chain data is still limited in that regard. Args: block_hash (str): the hash of the blockchain block for the query + reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: Dict {ss58: DelegatesDetails, ...} @@ -1253,6 +1312,7 @@ async def get_delegate_identities( module="Registry", storage_function="IdentityOf", block_hash=block_hash, + reuse_block_hash=reuse_block, ), session.get(DELEGATES_DETAILS_URL), ) @@ -1294,12 +1354,20 @@ async def get_delegate_identities( return all_delegates_details - async def is_hotkey_registered(self, netuid: int, hotkey_ss58: str) -> bool: + async def is_hotkey_registered( + self, + netuid: int, + hotkey_ss58: str, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> bool: """Checks to see if the hotkey is registered on a given netuid""" result = await self.substrate.query( module="SubtensorModule", storage_function="Uids", params=[netuid, hotkey_ss58], + block_hash=block_hash, + reuse_block_hash=reuse_block, ) if result is not None: return True @@ -1307,7 +1375,11 @@ async def is_hotkey_registered(self, netuid: int, hotkey_ss58: str) -> bool: return False async def get_uid_for_hotkey_on_subnet( - self, hotkey_ss58: str, netuid: int, block_hash: Optional[str] = None + self, + hotkey_ss58: str, + netuid: int, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Optional[int]: """ Retrieves the unique identifier (UID) for a neuron's hotkey on a specific subnet. @@ -1316,6 +1388,7 @@ async def get_uid_for_hotkey_on_subnet( hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. netuid (int): The unique identifier of the subnet. block_hash (Optional[str]): The blockchain block_hash representation of the block id. + reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: Optional[int]: The UID of the neuron if it is registered on the subnet, ``None`` otherwise. @@ -1327,21 +1400,29 @@ 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 result - async def weights_rate_limit(self, netuid: int) -> Optional[int]: + async def weights_rate_limit( + self, netuid: int, block_hash: Optional[str] = None, reuse_block: bool = False + ) -> Optional[int]: """ Returns network WeightsSetRateLimit hyperparameter. Args: netuid (int): The unique identifier of the subnetwork. + block_hash (Optional[str]): The blockchain block_hash representation of the block id. + reuse_block (bool): Whether to reuse the last-used blockchain block hash. Returns: Optional[int]: The value of the WeightsSetRateLimit hyperparameter, or ``None`` if the subnetwork does not exist or the parameter is not found. """ call = await self.get_hyperparameter( - param_name="WeightsSetRateLimit", netuid=netuid + param_name="WeightsSetRateLimit", + netuid=netuid, + block_hash=block_hash, + reuse_block=reuse_block, ) return None if call is None else int(call) From c496bb89092a9820e2a18df12f5631a138f7b9b6 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 27 Nov 2024 00:20:40 +0200 Subject: [PATCH 2/2] Update tests --- tests/unit_tests/test_async_subtensor.py | 30 +++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index c90309e808..c09a1499c1 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -306,6 +306,7 @@ async def test_get_subnet_burn_cost(subtensor, mocker): method="get_network_registration_cost", params=[], block_hash=fake_block_hash, + reuse_block=False, ) @@ -329,6 +330,7 @@ async def test_get_total_subnets(subtensor, mocker): storage_function="TotalNetworks", params=[], block_hash=fake_block_hash, + reuse_block_hash=False, ) @@ -361,7 +363,7 @@ async def test_get_subnets(subtensor, mocker, records, response): module="SubtensorModule", storage_function="NetworksAdded", block_hash=fake_block_hash, - reuse_block_hash=True, + reuse_block_hash=False, ) assert result == response @@ -498,6 +500,7 @@ async def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): storage_function="Stake", params=["hotkey", "coldkey"], block_hash=None, + reuse_block_hash=False, ) assert result == spy_balance.from_rao.return_value spy_balance.from_rao.assert_called_once_with(mocked_substrate_query.return_value) @@ -1503,6 +1506,7 @@ async def mock_query_map(**_): storage_function="Weights", params=[fake_netuid], block_hash=fake_block_hash, + reuse_block_hash=False, ) assert result == fake_weights @@ -1533,6 +1537,7 @@ async def mock_query_map(**_): storage_function="Bonds", params=[fake_netuid], block_hash=fake_block_hash, + reuse_block_hash=False, ) assert result == fake_bonds @@ -1627,6 +1632,7 @@ 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_decode_account_id.assert_called_once_with(fake_owner_account_id) mocked_does_hotkey_exist.assert_awaited_once_with( @@ -1659,6 +1665,7 @@ 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, ) mocked_decode_account_id.assert_called_once_with(None) assert result is None @@ -1692,6 +1699,7 @@ async def test_get_hotkey_owner_exists_but_does_not_exist_flag_false(subtensor, storage_function="Owner", params=[fake_hotkey_ss58], block_hash=fake_block_hash, + reuse_block_hash=False, ) mocked_decode_account_id.assert_called_once_with(fake_owner_account_id) mocked_does_hotkey_exist.assert_awaited_once_with( @@ -1985,6 +1993,7 @@ async def test_get_subnet_hyperparameters_success(subtensor, mocker): method="get_subnet_hyperparams", params=[fake_netuid], block_hash=fake_block_hash, + reuse_block=False, ) bytes_result = bytes.fromhex(fake_hex_bytes_result[2:]) mocked_from_vec_u8.assert_called_once_with(bytes_result) @@ -2009,6 +2018,7 @@ async def test_get_subnet_hyperparameters_no_data(subtensor, mocker): method="get_subnet_hyperparams", params=[fake_netuid], block_hash=None, + reuse_block=False, ) assert result == [] @@ -2037,6 +2047,7 @@ async def test_get_subnet_hyperparameters_without_0x_prefix(subtensor, mocker): method="get_subnet_hyperparams", params=[fake_netuid], block_hash=None, + reuse_block=False, ) bytes_result = bytes.fromhex(fake_hex_bytes_result) mocked_from_vec_u8.assert_called_once_with(bytes_result) @@ -2151,6 +2162,7 @@ async def test_get_delegate_identities(subtensor, mocker): module="Registry", storage_function="IdentityOf", block_hash=fake_block_hash, + reuse_block_hash=False, ) mock_session_get.assert_called_once_with(async_subtensor.DELEGATES_DETAILS_URL) @@ -2179,6 +2191,8 @@ async def test_is_hotkey_registered_true(subtensor, mocker): module="SubtensorModule", storage_function="Uids", params=[fake_netuid, fake_hotkey_ss58], + block_hash=None, + reuse_block_hash=False, ) assert result is True @@ -2204,6 +2218,8 @@ async def test_is_hotkey_registered_false(subtensor, mocker): module="SubtensorModule", storage_function="Uids", params=[fake_netuid, fake_hotkey_ss58], + block_hash=None, + reuse_block_hash=False, ) assert result is False @@ -2231,6 +2247,7 @@ 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 @@ -2258,6 +2275,7 @@ 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 @@ -2277,7 +2295,10 @@ async def test_weights_rate_limit_success(subtensor, mocker): # Asserts mocked_get_hyperparameter.assert_called_once_with( - param_name="WeightsSetRateLimit", netuid=fake_netuid + param_name="WeightsSetRateLimit", + netuid=fake_netuid, + block_hash=None, + reuse_block=False, ) assert result == fake_rate_limit @@ -2297,7 +2318,10 @@ async def test_weights_rate_limit_none(subtensor, mocker): # Asserts mocked_get_hyperparameter.assert_called_once_with( - param_name="WeightsSetRateLimit", netuid=fake_netuid + param_name="WeightsSetRateLimit", + netuid=fake_netuid, + block_hash=None, + reuse_block=False, ) assert result is None