diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index cf9c4f6469..46d0fb4ff7 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1322,21 +1322,53 @@ async def get_all_commitments( ) return result - async def get_all_metagraphs_info( + async def get_all_ema_tao_inflow( self, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, + ) -> dict[int, tuple[int, Balance]]: + """ + Query EMA TAO flow for all subnets using query_map. + + The EMA TAO flow represents the exponential moving average of TAO flowing into or out of a subnet. Negative + values indicate net outflow. + + Args: + block: The blockchain block number for the query. + block_hash: The hash of the blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used block hash when retrieving info. + + Returns: + Dict mapping netuid -> (block_number, Balance). + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.query_map( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + block_hash=block_hash, + ) + tao_inflow_ema = {} + async for netuid, (block_updated, tao_bits) in query: + ema_value = int(fixed_to_float(tao_bits)) + tao_inflow_ema[netuid] = (block_updated, Balance.from_rao(ema_value)) + return tao_inflow_ema + + async def get_all_metagraphs_info( + self, all_mechanisms: bool = False, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Optional[list[MetagraphInfo]]: """ Retrieves a list of MetagraphInfo objects for all subnets Parameters: + all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to perform the query. reuse_block: Whether to reuse the last-used block hash when retrieving info. - all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. Returns: List of MetagraphInfo objects for all existing subnets. @@ -2288,6 +2320,40 @@ async def get_existential_deposit( return Balance.from_rao(getattr(result, "value", 0)) + async def get_ema_tao_inflow( + self, + netuid: int, + block: Optional[int] = None, + ) -> Optional[tuple[int, Balance]]: + """ + Query EMA TAO flow for all subnets using query_map. + + The EMA TAO flow represents the exponential moving average of TAO flowing into or out of a subnet. Negative + values indicate net outflow. + + Args: + netuid: The unique identifier of the subnetwork. + block: The block number to retrieve the commitment from. + + Returns: + The tuple with block_number, Balance + """ + block_hash = await self.determine_block_hash(block) + query = await self.substrate.query( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + params=[netuid], + block_hash=block_hash, + ) + + # sn0 doesn't have EmaTaoInflow + if query is None: + return None + + block_updated, tao_bits = query.value + ema_value = int(fixed_to_float(tao_bits)) + return block_updated, Balance.from_rao(ema_value) + async def get_hotkey_owner( self, hotkey_ss58: str, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 9b9a992a79..073f9f8e18 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -874,17 +874,45 @@ def get_all_commitments( ) return result - def get_all_metagraphs_info( + def get_all_ema_tao_inflow( self, block: Optional[int] = None, + ) -> dict[int, tuple[int, Balance]]: + """ + Query EMA TAO flow for all subnets using query_map. + + The EMA TAO flow represents the exponential moving average of TAO flowing + into or out of a subnet. Negative values indicate net outflow. + + Args: + block: The block number to retrieve the commitment from. + + Returns: + Dict mapping netuid -> (block_number, Balance). + """ + block_hash = self.determine_block_hash(block) + query = self.substrate.query_map( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + block_hash=block_hash, + ) + tao_inflow_ema = {} + for netuid, (block_updated, tao_bits) in query: + ema_value = int(fixed_to_float(tao_bits)) + tao_inflow_ema[netuid] = (block_updated, Balance.from_rao(ema_value)) + return tao_inflow_ema + + def get_all_metagraphs_info( + self, all_mechanisms: bool = False, + block: Optional[int] = None, ) -> Optional[list[MetagraphInfo]]: """ Retrieves a list of MetagraphInfo objects for all subnets Parameters: - block: The blockchain block number for the query. all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. + block: The blockchain block number for the query. Returns: List of MetagraphInfo objects for all existing subnets. @@ -1598,6 +1626,40 @@ def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balan return Balance.from_rao(getattr(result, "value", 0)) + def get_ema_tao_inflow( + self, + netuid: int, + block: Optional[int] = None, + ) -> Optional[tuple[int, Balance]]: + """ + Query EMA TAO flow for all subnets using query_map. + + The EMA TAO flow represents the exponential moving average of TAO flowing into or out of a subnet. Negative + values indicate net outflow. + + Args: + netuid: The unique identifier of the subnetwork. + block: The block number to retrieve the commitment from. + + Returns: + The tuple with block_number, Balance + """ + block_hash = self.determine_block_hash(block) + query = self.substrate.query( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + params=[netuid], + block_hash=block_hash, + ) + + # sn0 doesn't have EmaTaoInflow + if query is None: + return None + + block_updated, tao_bits = query.value + ema_value = int(fixed_to_float(tao_bits)) + return block_updated, Balance.from_rao(ema_value) + def get_hotkey_owner( self, hotkey_ss58: str, block: Optional[int] = None ) -> Optional[str]: diff --git a/bittensor/extras/subtensor_api/subnets.py b/bittensor/extras/subtensor_api/subnets.py index 6c0ef8bb59..8f0c5aafc5 100644 --- a/bittensor/extras/subtensor_api/subnets.py +++ b/bittensor/extras/subtensor_api/subnets.py @@ -15,8 +15,10 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.burned_register = subtensor.burned_register self.commit_reveal_enabled = subtensor.commit_reveal_enabled self.difficulty = subtensor.difficulty + self.get_all_ema_tao_inflow = subtensor.get_all_ema_tao_inflow self.get_all_subnets_info = subtensor.get_all_subnets_info self.get_all_subnets_netuid = subtensor.get_all_subnets_netuid + self.get_ema_tao_inflow = subtensor.get_ema_tao_inflow self.get_parents = subtensor.get_parents self.get_children = subtensor.get_children self.get_children_pending = subtensor.get_children_pending diff --git a/bittensor/extras/subtensor_api/utils.py b/bittensor/extras/subtensor_api/utils.py index 37351f6cb5..6d74f7d577 100644 --- a/bittensor/extras/subtensor_api/utils.py +++ b/bittensor/extras/subtensor_api/utils.py @@ -44,6 +44,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.inner_subtensor.get_admin_freeze_window ) subtensor.get_all_commitments = subtensor.inner_subtensor.get_all_commitments + subtensor.get_all_ema_tao_inflow = subtensor.inner_subtensor.get_all_ema_tao_inflow subtensor.get_all_metagraphs_info = ( subtensor.inner_subtensor.get_all_metagraphs_info ) @@ -74,6 +75,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_delegate_take = subtensor.inner_subtensor.get_delegate_take subtensor.get_delegated = subtensor.inner_subtensor.get_delegated subtensor.get_delegates = subtensor.inner_subtensor.get_delegates + subtensor.get_ema_tao_inflow = subtensor.inner_subtensor.get_ema_tao_inflow subtensor.get_existential_deposit = ( subtensor.inner_subtensor.get_existential_deposit ) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 14f6f94493..5c337eac02 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -821,18 +821,16 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): bob_sn = TestSubnet(subtensor) bob_sn.execute_one(REGISTER_SUBNET(bob_wallet)) - print(">>> bob_sn", bob_sn.netuid) block = subtensor.chain.get_current_block() metagraph_info = subtensor.metagraphs.get_metagraph_info( netuid=bob_sn.netuid, block=block ) - print(">>> metagraph_info", metagraph_info) assert metagraph_info.owner_coldkey == bob_wallet.coldkey.ss58_address assert metagraph_info.owner_hotkey == bob_wallet.hotkey.ss58_address - metagraph_infos = subtensor.metagraphs.get_all_metagraphs_info(block) + metagraph_infos = subtensor.metagraphs.get_all_metagraphs_info(block=block) assert len(metagraph_infos) == 4 assert metagraph_infos[-1] == metagraph_info @@ -1085,7 +1083,9 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): assert metagraph_info.owner_coldkey == bob_wallet.coldkey.ss58_address assert metagraph_info.owner_hotkey == bob_wallet.hotkey.ss58_address - metagraph_infos = await async_subtensor.metagraphs.get_all_metagraphs_info(block) + metagraph_infos = await async_subtensor.metagraphs.get_all_metagraphs_info( + block=block + ) assert len(metagraph_infos) == 4 assert metagraph_infos[-1] == metagraph_info diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 830996dbc1..e898b3fdda 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4885,3 +4885,75 @@ async def test_set_root_claim_type(mocker, subtensor): wait_for_finalization=True, ) assert response == mocked_set_root_claim_type_extrinsic.return_value + + +@pytest.mark.asyncio +async def test_get_all_ema_tao_inflow(subtensor, mocker): + """Test get_all_ema_tao_inflow returns correct values.""" + # Preps + fake_block = 123 + fake_netuid = 1 + fake_block_updated = 100 + fake_tao_bits = {"bits": 6520190} + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + fake_query_result = [(fake_netuid, (fake_block_updated, fake_tao_bits))] + + fake_result = mocker.AsyncMock(autospec=list) + fake_result.__aiter__.return_value = iter(fake_query_result) + + mocked_query_map = mocker.patch.object( + subtensor.substrate, + "query_map", + return_value=fake_result, + ) + mocked_fixed_to_float = mocker.patch.object( + async_subtensor, "fixed_to_float", return_value=1000000 + ) + + # Call + result = await subtensor.get_all_ema_tao_inflow(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block, None, False) + mocked_query_map.assert_awaited_once_with( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_fixed_to_float.assert_called_once_with(fake_tao_bits) + assert result == {fake_netuid: (fake_block_updated, Balance.from_rao(1000000))} + + +@pytest.mark.asyncio +async def test_get_ema_tao_inflow(subtensor, mocker): + """Test get_ema_tao_inflow returns correct values.""" + # Preps + fake_block = 123 + fake_netuid = 1 + fake_block_updated = 100 + fake_tao_bits = {"bits": 6520190} + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_query = mocker.patch.object( + subtensor.substrate, + "query", + return_value=mocker.Mock(value=(fake_block_updated, fake_tao_bits)), + ) + mocked_fixed_to_float = mocker.patch.object( + async_subtensor, "fixed_to_float", return_value=1000000 + ) + + # Call + result = await subtensor.get_ema_tao_inflow(netuid=fake_netuid, block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_awaited_once_with(fake_block) + mocked_query.assert_awaited_once_with( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + params=[fake_netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_fixed_to_float.assert_called_once_with(fake_tao_bits) + assert result == (fake_block_updated, Balance.from_rao(1000000)) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 17fdb16d76..5dc104ab51 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4968,3 +4968,67 @@ def test_set_root_claim_type(mocker, subtensor): wait_for_finalization=True, ) assert response == mocked_set_root_claim_type_extrinsic.return_value + + +def test_get_all_ema_tao_inflow(subtensor, mocker): + """Test get_all_ema_tao_inflow returns correct values.""" + # Preps + fake_block = 123 + fake_netuid = 1 + fake_block_updated = 100 + fake_tao_bits = {"bits": 6520190} + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + fake_query_result = [(fake_netuid, (fake_block_updated, fake_tao_bits))] + mock_query_map = mocker.patch.object( + subtensor.substrate, "query_map", return_value=fake_query_result + ) + mocked_fixed_to_float = mocker.patch.object( + subtensor_module, "fixed_to_float", return_value=1000000 + ) + + # Call + result = subtensor.get_all_ema_tao_inflow(block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(fake_block) + mock_query_map.assert_called_once_with( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_fixed_to_float.assert_called_once_with(fake_tao_bits) + assert result == {fake_netuid: (fake_block_updated, Balance.from_rao(1000000))} + + +def test_get_ema_tao_inflow(subtensor, mocker): + """Test get_ema_tao_inflow returns correct values.""" + # Preps + fake_block = 123 + fake_netuid = 1 + fake_block_updated = 100 + fake_tao_bits = {"bits": 6520190} + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_query = mocker.patch.object( + subtensor.substrate, + "query", + return_value=mocker.Mock(value=(fake_block_updated, fake_tao_bits)), + ) + mocked_fixed_to_float = mocker.patch.object( + subtensor_module, "fixed_to_float", return_value=1000000 + ) + + # Call + result = subtensor.get_ema_tao_inflow(netuid=fake_netuid, block=fake_block) + + # Asserts + mocked_determine_block_hash.assert_called_once_with(fake_block) + mocked_query.assert_called_once_with( + module="SubtensorModule", + storage_function="SubnetEmaTaoFlow", + params=[fake_netuid], + block_hash=mocked_determine_block_hash.return_value, + ) + mocked_fixed_to_float.assert_called_once_with(fake_tao_bits) + assert result == (fake_block_updated, Balance.from_rao(1000000))