diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index aa5fa9c5f1..5525a1eb37 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1921,6 +1921,42 @@ async def get_stake_add_fee( ) return Balance.from_rao(result) + async def get_subnet_info( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional["SubnetInfo"]: + """ + Retrieves detailed information about subnet within the Bittensor network. + This function provides comprehensive data on subnet, including its characteristics and operational parameters. + + Arguments: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + block_hash (Optional[str]): The hash of the block to retrieve the stake from. Do not specify if using block + or reuse_block + reuse_block (bool): Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. + + Gaining insights into the subnet's details assists in understanding the network's composition, the roles of + different subnets, and their unique features. + """ + result = await self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + if not result: + return None + return SubnetInfo.from_dict(result) + async def get_unstake_fee( self, amount: Balance, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 015b918cd7..5c7241d4dc 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1507,6 +1507,33 @@ def get_stake_add_fee( ) return Balance.from_rao(result) + def get_subnet_info( + self, netuid: int, block: Optional[int] = None + ) -> Optional["SubnetInfo"]: + """ + Retrieves detailed information about subnet within the Bittensor network. + This function provides comprehensive data on subnet, including its characteristics and operational parameters. + + Arguments: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. + + Gaining insights into the subnet's details assists in understanding the network's composition, the roles of + different subnets, and their unique features. + """ + result = self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + ) + if not result: + return None + return SubnetInfo.from_dict(result) + def get_unstake_fee( self, amount: Balance, diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index 8b8c9121e7..962f761d1e 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -24,6 +24,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_next_epoch_start_block = subtensor.get_next_epoch_start_block self.get_subnet_burn_cost = subtensor.get_subnet_burn_cost self.get_subnet_hyperparameters = subtensor.get_subnet_hyperparameters + self.get_subnet_info = subtensor.get_subnet_info self.get_subnet_owner_hotkey = subtensor.get_subnet_owner_hotkey self.get_subnet_reveal_period_epochs = subtensor.get_subnet_reveal_period_epochs self.get_subnet_validator_permits = subtensor.get_subnet_validator_permits diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 3f8cc7d36d..0ca9b1234a 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -84,6 +84,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_subnet_hyperparameters = ( subtensor._subtensor.get_subnet_hyperparameters ) + subtensor.get_subnet_info = subtensor._subtensor.get_subnet_info subtensor.get_subnet_owner_hotkey = subtensor._subtensor.get_subnet_owner_hotkey subtensor.get_subnet_reveal_period_epochs = ( subtensor._subtensor.get_subnet_reveal_period_epochs diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 0449a7b8ef..ddf8daf818 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3314,3 +3314,59 @@ async def test_get_subnet_validator_permits_is_none(subtensor, mocker): ) assert result is None + + +@pytest.mark.asyncio +async def test_get_subnet_info_success(mocker, subtensor): + """Test get_subnet_info returns correct data when subnet information is found.""" + # Prep + netuid = mocker.Mock() + block = mocker.Mock() + + mocker.patch.object(subtensor, "query_runtime_api") + mocker.patch.object( + async_subtensor.SubnetInfo, + "from_dict", + ) + + # Call + result = await subtensor.get_subnet_info(netuid=netuid, block=block) + + # Asserts + subtensor.query_runtime_api.assert_awaited_once_with( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + block_hash=None, + reuse_block=False, + ) + async_subtensor.SubnetInfo.from_dict.assert_called_once_with( + subtensor.query_runtime_api.return_value, + ) + assert result == async_subtensor.SubnetInfo.from_dict.return_value + + +@pytest.mark.asyncio +async def test_get_subnet_info_no_data(mocker, subtensor): + """Test get_subnet_info returns None.""" + # Prep + netuid = mocker.Mock() + block = mocker.Mock() + mocker.patch.object(async_subtensor.SubnetInfo, "from_dict") + mocker.patch.object(subtensor, "query_runtime_api", return_value=None) + + # Call + result = await subtensor.get_subnet_info(netuid=netuid, block=block) + + # Asserts + subtensor.query_runtime_api.assert_awaited_once_with( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + block_hash=None, + reuse_block=False, + ) + async_subtensor.SubnetInfo.from_dict.assert_not_called() + assert result is None diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 0db7c0cafe..163a89de6c 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3668,3 +3668,54 @@ def test_is_subnet_active(subtensor, mocker, query_return, expected): ) assert result == expected + + +# `geg_l_subnet_info` tests +def test_get_subnet_info_success(mocker, subtensor): + """Test get_subnet_info returns correct data when subnet information is found.""" + # Prep + netuid = mocker.Mock() + block = mocker.Mock() + + mocker.patch.object(subtensor, "query_runtime_api") + mocker.patch.object( + subtensor_module.SubnetInfo, + "from_dict", + ) + + # Call + result = subtensor.get_subnet_info(netuid=netuid, block=block) + + # Asserts + subtensor.query_runtime_api.assert_called_once_with( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + ) + subtensor_module.SubnetInfo.from_dict.assert_called_once_with( + subtensor.query_runtime_api.return_value, + ) + assert result == subtensor_module.SubnetInfo.from_dict.return_value + + +def test_get_subnet_info_no_data(mocker, subtensor): + """Test get_subnet_info returns None.""" + # Prep + netuid = mocker.Mock() + block = mocker.Mock() + mocker.patch.object(subtensor_module.SubnetInfo, "from_dict") + mocker.patch.object(subtensor, "query_runtime_api", return_value=None) + + # Call + result = subtensor.get_subnet_info(netuid=netuid, block=block) + + # Asserts + subtensor.query_runtime_api.assert_called_once_with( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + ) + subtensor_module.SubnetInfo.from_dict.assert_not_called() + assert result is None