From ad3b24c65f25e43ffb27d8bdbd4bb325ab0c8d30 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 3 Jun 2025 16:42:24 +0200 Subject: [PATCH 1/8] [WIP] Add get_parents method to subtensor --- bittensor/core/async_subtensor.py | 45 +++++++++++++++++++++++++ bittensor/core/subtensor.py | 36 ++++++++++++++++++++ bittensor/core/subtensor_api/subnets.py | 1 + 3 files changed, 82 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index da7d75a82f..fdbdf71c50 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -973,6 +973,51 @@ async def get_block_hash(self, block: Optional[int] = None) -> str: else: return await self.substrate.get_chain_head() + async def get_parents( + self, + hotkey: str, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> tuple[bool, list[tuple[float, str]], str]: + """ + This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys + storage function to get the children and formats them before returning as a tuple. + + Arguments: + hotkey (str): The child hotkey SS58. + netuid (int): The netuid value. + block (Optional[int]): The block number for which the children are to be retrieved. + 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 tuple containing a boolean indicating success or failure, a list of formatted + parents [(proportion, parent)], and an error message (if applicable) + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + try: + parents = await self.substrate.query( + module="SubtensorModule", + storage_function="ParentKeys", + params=[hotkey, netuid], + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + if parents: + formatted_parents = [] + for proportion, parent 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 True, formatted_parents, "" + else: + return True, [], "" + except SubstrateRequestException as e: + return False, [], format_error_message(e) + async def get_children( self, hotkey: str, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 406295e330..ed24d9da44 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -751,6 +751,42 @@ def get_hyperparameter( return getattr(result, "value", result) + def get_parents( + self, hotkey: str, netuid: int, block: Optional[int] = None + ) -> tuple[bool, list[tuple[float, str]], str]: + """ + This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys + storage function to get the children and formats them before returning as a tuple. + + Arguments: + hotkey (str): The child hotkey SS58. + netuid (int): The netuid. + block (Optional[int]): The block number for which the children are to be retrieved. + + Returns: + A tuple containing a boolean indicating success or failure, a list of formatted + parents [(proportion, parent)], and an error message (if applicable) + """ + try: + parents = self.substrate.query( + module="SubtensorModule", + storage_function="ParentKeys", + params=[hotkey, netuid], + block_hash=self.determine_block_hash(block), + ) + if parents: + formatted_parents = [] + for proportion, parent 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 True, formatted_parents, "" + else: + return True, [], "" + except SubstrateRequestException as e: + return False, [], format_error_message(e) + def get_children( self, hotkey: str, netuid: int, block: Optional[int] = None ) -> tuple[bool, list[tuple[float, str]], str]: diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index 962f761d1e..ddeedaf1fa 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -14,6 +14,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.bonds = subtensor.bonds self.difficulty = subtensor.difficulty self.get_all_subnets_info = subtensor.get_all_subnets_info + self.get_parents = subtensor.get_parents self.get_children = subtensor.get_children self.get_children_pending = subtensor.get_children_pending self.get_current_weight_commit_info = subtensor.get_current_weight_commit_info From ea720ba37f642a904b77096fe3c6508792b2a30a Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 3 Jun 2025 17:06:20 +0200 Subject: [PATCH 2/8] Add to subtensorAPI and tests --- bittensor/core/subtensor_api/utils.py | 1 + tests/e2e_tests/test_hotkeys.py | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 0ca9b1234a..0ddd28bcc7 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -36,6 +36,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_balance = subtensor._subtensor.get_balance subtensor.get_balances = subtensor._subtensor.get_balances subtensor.get_block_hash = subtensor._subtensor.get_block_hash + subtensor.get_parents = subtensor._subtensor.get_parents subtensor.get_children = subtensor._subtensor.get_children subtensor.get_children_pending = subtensor._subtensor.get_children_pending subtensor.get_commitment = subtensor._subtensor.get_commitment diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 3a699f08d0..7474d470db 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -69,7 +69,7 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): ) is True ) - logging.console.success(f"✅ Test [green]test_hotkeys[/green] passed") + logging.console.success("✅ Test [green]test_hotkeys[/green] passed") @pytest.mark.asyncio @@ -276,6 +276,14 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w assert success is True assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + success_, parent_, error_ = subtensor.get_parents( + bob_wallet.hotkey.ss58_address, dave_subnet_netuid + ) + + assert error_ == "" + assert success_ is True + assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] + # pending queue is empty pending, cooldown = subtensor.get_children_pending( alice_wallet.hotkey.ss58_address, From cb5c95655d83af008a0579a37e60c18b02cde741 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 3 Jun 2025 19:00:19 +0200 Subject: [PATCH 3/8] ChatGPT added unit tests --- tests/unit_tests/test_async_subtensor.py | 99 ++++++++++++++++++++++++ tests/unit_tests/test_subtensor.py | 93 ++++++++++++++++++++++ 2 files changed, 192 insertions(+) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index a023be1c62..21afbaa1e9 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -1981,6 +1981,105 @@ async def test_get_children_substrate_request_exception(subtensor, mocker): assert result == (False, [], "Formatted error message") +@pytest.mark.asyncio +async def test_get_parents_success(subtensor, mocker): + """Tests get_parents when parents are successfully retrieved and formatted.""" + # Preps + fake_hotkey = "valid_hotkey" + fake_netuid = 1 + fake_parents = mocker.Mock( + value=[ + (1000, ["parent_key_1"]), + (2000, ["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"), + ] + + # Call + result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + + # Asserts + mocked_query.assert_called_once_with( + block_hash=None, + 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")] + ) + assert result == (True, expected_formatted_parents, "") + + +@pytest.mark.asyncio +async def test_get_parents_no_parents(subtensor, mocker): + """Tests get_parents when there are no parents to retrieve.""" + # Preps + fake_hotkey = "valid_hotkey" + fake_netuid = 1 + fake_parents = [] + + mocked_query = mocker.AsyncMock(return_value=fake_parents) + subtensor.substrate.query = mocked_query + + # Call + result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + + # Asserts + mocked_query.assert_called_once_with( + block_hash=None, + module="SubtensorModule", + storage_function="ParentKeys", + params=[fake_hotkey, fake_netuid], + reuse_block_hash=False, + ) + assert result == (True, [], "") + + +@pytest.mark.asyncio +async def test_get_parents_substrate_request_exception(subtensor, mocker): + """Tests get_parents when SubstrateRequestException is raised.""" + # Preps + fake_hotkey = "valid_hotkey" + fake_netuid = 1 + fake_exception = async_subtensor.SubstrateRequestException("Test Exception") + + mocked_query = mocker.AsyncMock(side_effect=fake_exception) + subtensor.substrate.query = mocked_query + + mocked_format_error_message = mocker.Mock(return_value="Formatted error message") + mocker.patch.object( + async_subtensor, "format_error_message", mocked_format_error_message + ) + + # Call + result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + + # Asserts + mocked_query.assert_called_once_with( + block_hash=None, + module="SubtensorModule", + storage_function="ParentKeys", + 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") + + @pytest.mark.asyncio async def test_get_children_pending(mock_substrate, subtensor): mock_substrate.query.return_value.value = [ diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 2499db8248..a8fec2c269 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3746,3 +3746,96 @@ def test_get_next_epoch_start_block(mocker, subtensor, call_return, expected): ) subtensor.tempo.assert_called_once_with(netuid=netuid, block=block) assert result == expected + + +def test_get_parents_success(subtensor, mocker): + """Tests get_parents when parents are successfully retrieved and formatted.""" + # Preps + fake_hotkey = "valid_hotkey" + fake_netuid = 1 + fake_parents = mocker.Mock( + value=[ + (1000, ["parent_key_1"]), + (2000, ["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"), + ] + + # Call + result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + + # Asserts + mocked_query.assert_called_once_with( + block_hash=None, + module="SubtensorModule", + 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 == (True, expected_formatted_parents, "") + + +def test_get_parents_no_parents(subtensor, mocker): + """Tests get_parents when there are no parents to retrieve.""" + # Preps + fake_hotkey = "valid_hotkey" + fake_netuid = 1 + fake_parents = [] + + mocked_query = mocker.MagicMock(return_value=fake_parents) + subtensor.substrate.query = mocked_query + + # Call + result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + + # Asserts + mocked_query.assert_called_once_with( + block_hash=None, + module="SubtensorModule", + storage_function="ParentKeys", + params=[fake_hotkey, fake_netuid], + ) + assert result == (True, [], "") + + +def test_get_parents_substrate_request_exception(subtensor, mocker): + """Tests get_parents when SubstrateRequestException is raised.""" + # Preps + fake_hotkey = "valid_hotkey" + fake_netuid = 1 + fake_exception = subtensor_module.SubstrateRequestException("Test Exception") + + mocked_query = mocker.MagicMock(side_effect=fake_exception) + subtensor.substrate.query = mocked_query + + mocked_format_error_message = mocker.Mock(return_value="Formatted error message") + mocker.patch.object( + subtensor_module, "format_error_message", mocked_format_error_message + ) + + # Call + result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + + # Asserts + mocked_query.assert_called_once_with( + block_hash=None, + module="SubtensorModule", + storage_function="ParentKeys", + params=[fake_hotkey, fake_netuid], + ) + mocked_format_error_message.assert_called_once_with(fake_exception) + assert result == (False, [], "Formatted error message") From d6e9e24a4b07a525198e30cc01f1c26e6b21c8fb Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 3 Jun 2025 19:39:49 +0200 Subject: [PATCH 4/8] Removed try:except --- bittensor/core/async_subtensor.py | 37 ++++++++++++++----------------- bittensor/core/subtensor.py | 35 +++++++++++++---------------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index fdbdf71c50..41b04bfc26 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -997,26 +997,23 @@ async def get_parents( parents [(proportion, parent)], and an error message (if applicable) """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - try: - parents = await self.substrate.query( - module="SubtensorModule", - storage_function="ParentKeys", - params=[hotkey, netuid], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - if parents: - formatted_parents = [] - for proportion, parent 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 True, formatted_parents, "" - else: - return True, [], "" - except SubstrateRequestException as e: - return False, [], format_error_message(e) + parents = await self.substrate.query( + module="SubtensorModule", + storage_function="ParentKeys", + params=[hotkey, netuid], + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + if parents: + formatted_parents = [] + for proportion, parent 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 True, formatted_parents, "" + else: + return True, [], "" async def get_children( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ed24d9da44..4058882392 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -767,25 +767,22 @@ def get_parents( A tuple containing a boolean indicating success or failure, a list of formatted parents [(proportion, parent)], and an error message (if applicable) """ - try: - parents = self.substrate.query( - module="SubtensorModule", - storage_function="ParentKeys", - params=[hotkey, netuid], - block_hash=self.determine_block_hash(block), - ) - if parents: - formatted_parents = [] - for proportion, parent 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 True, formatted_parents, "" - else: - return True, [], "" - except SubstrateRequestException as e: - return False, [], format_error_message(e) + parents = self.substrate.query( + module="SubtensorModule", + storage_function="ParentKeys", + params=[hotkey, netuid], + block_hash=self.determine_block_hash(block), + ) + if parents: + formatted_parents = [] + for proportion, parent 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 True, formatted_parents, "" + else: + return True, [], "" def get_children( self, hotkey: str, netuid: int, block: Optional[int] = None From 4845a7e9411f8398a9e4eed9652caa198d57d30e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 3 Jun 2025 19:53:03 +0200 Subject: [PATCH 5/8] Changed return types and fixed tests to reflect this --- bittensor/core/async_subtensor.py | 9 ++++----- bittensor/core/subtensor.py | 5 ++--- tests/e2e_tests/test_hotkeys.py | 6 +----- tests/unit_tests/test_async_subtensor.py | 23 ++++------------------ tests/unit_tests/test_subtensor.py | 25 ++++++------------------ 5 files changed, 17 insertions(+), 51 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 41b04bfc26..839420e916 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -980,7 +980,7 @@ async def get_parents( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> tuple[bool, list[tuple[float, str]], str]: + ) -> list[tuple[float, str]]: """ This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys storage function to get the children and formats them before returning as a tuple. @@ -993,8 +993,7 @@ async def get_parents( reuse_block (bool): Whether to reuse the last-used block hash. Returns: - A tuple containing a boolean indicating success or failure, a list of formatted - parents [(proportion, parent)], and an error message (if applicable) + A list of formatted parents [(proportion, parent)] """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) parents = await self.substrate.query( @@ -1011,9 +1010,9 @@ async def get_parents( formatted_child = decode_account_id(parent[0]) normalized_proportion = u64_normalized_float(proportion) formatted_parents.append((normalized_proportion, formatted_child)) - return True, formatted_parents, "" + return formatted_parents else: - return True, [], "" + return [] async def get_children( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 4058882392..22bc003248 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -753,7 +753,7 @@ def get_hyperparameter( def get_parents( self, hotkey: str, netuid: int, block: Optional[int] = None - ) -> tuple[bool, list[tuple[float, str]], str]: + ) -> list[tuple[float, str]]: """ This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys storage function to get the children and formats them before returning as a tuple. @@ -764,8 +764,7 @@ def get_parents( block (Optional[int]): The block number for which the children are to be retrieved. Returns: - A tuple containing a boolean indicating success or failure, a list of formatted - parents [(proportion, parent)], and an error message (if applicable) + A list of formatted parents [(proportion, parent)] """ parents = self.substrate.query( module="SubtensorModule", diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 7474d470db..8bd8cfbd63 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -276,12 +276,8 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w assert success is True assert children == [(1.0, bob_wallet.hotkey.ss58_address)] - success_, parent_, error_ = subtensor.get_parents( - bob_wallet.hotkey.ss58_address, dave_subnet_netuid - ) + parent_ = subtensor.get_parents(bob_wallet.hotkey.ss58_address, dave_subnet_netuid) - assert error_ == "" - assert success_ is True assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] # pending queue is empty diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 21afbaa1e9..a723f4ed83 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2021,7 +2021,7 @@ async def test_get_parents_success(subtensor, mocker): mocked_decode_account_id.assert_has_calls( [mocker.call("parent_key_1"), mocker.call("parent_key_2")] ) - assert result == (True, expected_formatted_parents, "") + assert result == expected_formatted_parents @pytest.mark.asyncio @@ -2046,7 +2046,7 @@ async def test_get_parents_no_parents(subtensor, mocker): params=[fake_hotkey, fake_netuid], reuse_block_hash=False, ) - assert result == (True, [], "") + assert result == [] @pytest.mark.asyncio @@ -2060,24 +2060,9 @@ async def test_get_parents_substrate_request_exception(subtensor, mocker): mocked_query = mocker.AsyncMock(side_effect=fake_exception) subtensor.substrate.query = mocked_query - mocked_format_error_message = mocker.Mock(return_value="Formatted error message") - mocker.patch.object( - async_subtensor, "format_error_message", mocked_format_error_message - ) - # Call - result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) - - # Asserts - mocked_query.assert_called_once_with( - block_hash=None, - module="SubtensorModule", - storage_function="ParentKeys", - 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") + with pytest.raises(async_subtensor.SubstrateRequestException): + await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) @pytest.mark.asyncio diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index a8fec2c269..a51150e033 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -8,6 +8,7 @@ from async_substrate_interface import sync_substrate from async_substrate_interface.types import ScaleObj import websockets +from substrateinterface.exceptions import SubstrateRequestException from bittensor import StakeInfo from bittensor.core import settings @@ -3786,7 +3787,7 @@ def test_get_parents_success(subtensor, mocker): mocked_decode_account_id.assert_has_calls( [mocker.call("parent_key_1"), mocker.call("parent_key_2")] ) - assert result == (True, expected_formatted_parents, "") + assert result == expected_formatted_parents def test_get_parents_no_parents(subtensor, mocker): @@ -3809,7 +3810,7 @@ def test_get_parents_no_parents(subtensor, mocker): storage_function="ParentKeys", params=[fake_hotkey, fake_netuid], ) - assert result == (True, [], "") + assert result == [] def test_get_parents_substrate_request_exception(subtensor, mocker): @@ -3817,25 +3818,11 @@ def test_get_parents_substrate_request_exception(subtensor, mocker): # Preps fake_hotkey = "valid_hotkey" fake_netuid = 1 - fake_exception = subtensor_module.SubstrateRequestException("Test Exception") + fake_exception = SubstrateRequestException("Test Exception") mocked_query = mocker.MagicMock(side_effect=fake_exception) subtensor.substrate.query = mocked_query - mocked_format_error_message = mocker.Mock(return_value="Formatted error message") - mocker.patch.object( - subtensor_module, "format_error_message", mocked_format_error_message - ) - # Call - result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) - - # Asserts - mocked_query.assert_called_once_with( - block_hash=None, - module="SubtensorModule", - storage_function="ParentKeys", - params=[fake_hotkey, fake_netuid], - ) - mocked_format_error_message.assert_called_once_with(fake_exception) - assert result == (False, [], "Formatted error message") + with pytest.raises(SubstrateRequestException): + subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) From a87736db9e26c2e7d1a624ae005ec3fef76251db Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 3 Jun 2025 19:54:48 +0200 Subject: [PATCH 6/8] Oopsie --- bittensor/core/subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 22bc003248..4325f6b2cb 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -779,9 +779,9 @@ def get_parents( formatted_child = decode_account_id(parent[0]) normalized_proportion = u64_normalized_float(proportion) formatted_parents.append((normalized_proportion, formatted_child)) - return True, formatted_parents, "" + return formatted_parents else: - return True, [], "" + return [] def get_children( self, hotkey: str, netuid: int, block: Optional[int] = None From a5afbdd6ae1913107cd8c45613985e6d715a7a97 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 3 Jun 2025 11:37:38 -0700 Subject: [PATCH 7/8] remove nested else --- bittensor/core/async_subtensor.py | 14 +++++++------- bittensor/core/subtensor.py | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 839420e916..c4503b4142 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -986,11 +986,11 @@ async def get_parents( storage function to get the children and formats them before returning as a tuple. Arguments: - hotkey (str): The child hotkey SS58. - netuid (int): The netuid value. - block (Optional[int]): The block number for which the children are to be retrieved. - 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. + hotkey: The child hotkey SS58. + netuid: The netuid value. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: A list of formatted parents [(proportion, parent)] @@ -1011,8 +1011,8 @@ async def get_parents( normalized_proportion = u64_normalized_float(proportion) formatted_parents.append((normalized_proportion, formatted_child)) return formatted_parents - else: - return [] + + return [] async def get_children( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 4325f6b2cb..bb0806159d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -759,9 +759,9 @@ def get_parents( storage function to get the children and formats them before returning as a tuple. Arguments: - hotkey (str): The child hotkey SS58. - netuid (int): The netuid. - block (Optional[int]): The block number for which the children are to be retrieved. + hotkey: The child hotkey SS58. + netuid: The netuid. + block: The block number for which the children are to be retrieved. Returns: A list of formatted parents [(proportion, parent)] @@ -780,8 +780,8 @@ def get_parents( normalized_proportion = u64_normalized_float(proportion) formatted_parents.append((normalized_proportion, formatted_child)) return formatted_parents - else: - return [] + + return [] def get_children( self, hotkey: str, netuid: int, block: Optional[int] = None From 31c840651c35109424a531c8a24dbb2242b7ddb5 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 3 Jun 2025 11:38:02 -0700 Subject: [PATCH 8/8] remove an unused test case --- tests/unit_tests/test_subtensor.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index a51150e033..11fa933425 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -8,7 +8,6 @@ from async_substrate_interface import sync_substrate from async_substrate_interface.types import ScaleObj import websockets -from substrateinterface.exceptions import SubstrateRequestException from bittensor import StakeInfo from bittensor.core import settings @@ -3811,18 +3810,3 @@ def test_get_parents_no_parents(subtensor, mocker): params=[fake_hotkey, fake_netuid], ) assert result == [] - - -def test_get_parents_substrate_request_exception(subtensor, mocker): - """Tests get_parents when SubstrateRequestException is raised.""" - # Preps - fake_hotkey = "valid_hotkey" - fake_netuid = 1 - fake_exception = SubstrateRequestException("Test Exception") - - mocked_query = mocker.MagicMock(side_effect=fake_exception) - subtensor.substrate.query = mocked_query - - # Call - with pytest.raises(SubstrateRequestException): - subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid)