From efee1eb9e4281cd96e261da760904b7f209a723b Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Nov 2024 16:55:12 -0800 Subject: [PATCH 1/7] added tests from `AsyncSubtensor.get_netuids_for_hotkey` until `AsyncSubtensor.neurons_lite` --- bittensor/core/async_subtensor.py | 3 - tests/unit_tests/test_async_subtensor.py | 336 +++++++++++++++++++++++ 2 files changed, 336 insertions(+), 3 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1588f2f23..aa5cddc4a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -677,9 +677,6 @@ async def get_hyperparameter( reuse_block_hash=reuse_block, ) - if result is None: - return None - return result async def filter_netuids_by_registered_hotkeys( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index dac1607a3..a34afb92a 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -629,3 +629,339 @@ async def test_get_total_stake_for_hotkey(subtensor, mocker): ) mocked_substrate_query_multiple.assert_called_once() assert result == {0: async_subtensor.Balance(1)} + + +@pytest.mark.parametrize( + "records, response", + [([(0, True), (1, False), (3, False), (3, True)], [0, 3]), ([], [])], + ids=["with records", "empty-records"], +) +@pytest.mark.asyncio +async def test_get_netuids_for_hotkey(subtensor, mocker, records, response): + """Tests get_netuids_for_hotkey method.""" + # Preps + fake_result = mocker.AsyncMock(autospec=list) + fake_result.records = records + fake_result.__aiter__.return_value = iter(records) + + mocked_substrate_query_map = mocker.AsyncMock( + autospec=async_subtensor.AsyncSubstrateInterface.query_map, + return_value=fake_result, + ) + + subtensor.substrate.query_map = mocked_substrate_query_map + fake_hotkey_ss58 = "hotkey_58" + fake_block_hash = None + + # Call + result = await subtensor.get_netuids_for_hotkey( + hotkey_ss58=fake_hotkey_ss58, block_hash=fake_block_hash, reuse_block=True + ) + + # Assertions + mocked_substrate_query_map.assert_called_once_with( + module="SubtensorModule", + storage_function="IsNetworkMember", + params=[fake_hotkey_ss58], + block_hash=fake_block_hash, + reuse_block_hash=True, + ) + assert result == response + + +@pytest.mark.asyncio +async def test_subnet_exists(subtensor, mocker): + """Tests subnet_exists method .""" + # Preps + fake_netuid = 1 + fake_block_hash = "block_hash" + fake_reuse_block_hash = True + + mocked_substrate_query = mocker.AsyncMock( + autospec=async_subtensor.AsyncSubstrateInterface.query + ) + subtensor.substrate.query = mocked_substrate_query + + # Call + result = await subtensor.subnet_exists( + netuid=fake_netuid, + block_hash=fake_block_hash, + reuse_block=fake_reuse_block_hash, + ) + + # Asserts + mocked_substrate_query.assert_called_once_with( + module="SubtensorModule", + 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 + + +@pytest.mark.asyncio +async def test_get_hyperparameter_happy_path(subtensor, mocker): + """Tests get_hyperparameter method with happy path.""" + # Preps + fake_param_name = "param_name" + fake_netuid = 1 + fake_block_hash = "block_hash" + fake_reuse_block_hash = True + + # kind of fake subnet exists + mocked_subtensor_subnet_exists = mocker.AsyncMock(return_value=True) + subtensor.subnet_exists = mocked_subtensor_subnet_exists + + mocked_substrate_query = mocker.AsyncMock( + autospec=async_subtensor.AsyncSubstrateInterface.query + ) + subtensor.substrate.query = mocked_substrate_query + + # Call + result = await subtensor.get_hyperparameter( + param_name=fake_param_name, + netuid=fake_netuid, + block_hash=fake_block_hash, + reuse_block=fake_reuse_block_hash, + ) + + # Assertions + mocked_subtensor_subnet_exists.assert_called_once() + mocked_substrate_query.assert_called_once_with( + module="SubtensorModule", + 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 + + +@pytest.mark.asyncio +async def test_get_hyperparameter_if_subnet_does_not_exist(subtensor, mocker): + """Tests get_hyperparameter method if subnet does not exist.""" + # Preps + # kind of fake subnet doesn't exist + mocked_subtensor_subnet_exists = mocker.AsyncMock(return_value=False) + subtensor.subnet_exists = mocked_subtensor_subnet_exists + + mocked_substrate_query = mocker.AsyncMock( + autospec=async_subtensor.AsyncSubstrateInterface.query + ) + subtensor.substrate.query = mocked_substrate_query + + # Call + result = await subtensor.get_hyperparameter(mocker.Mock(), mocker.Mock()) + + # Assertions + mocked_subtensor_subnet_exists.assert_called_once() + mocked_substrate_query.assert_not_called() + assert result is None + + +@pytest.mark.parametrize( + "all_netuids, filter_for_netuids, response", + [([1, 2], [3, 4], []), ([1, 2], [1, 3], [1]), ([1, 2], None, [1, 2])], + ids=[ + "all arguments -> no comparison", + "all arguments -> is comparison", + "not filter_for_netuids", + ], +) +@pytest.mark.asyncio +async def test_filter_netuids_by_registered_hotkeys( + subtensor, mocker, all_netuids, filter_for_netuids, response +): + """Tests filter_netuids_by_registered_hotkeys method.""" + # Preps + fake_wallet_1 = mocker.Mock(autospec=async_subtensor.Wallet) + fake_wallet_1.hotkey.ss58_address = "ss58_address_1" + fake_wallet_2 = mocker.Mock(autospec=async_subtensor.Wallet) + fake_wallet_2.hotkey.ss58_address = "ss58_address_2" + + fake_all_netuids = all_netuids + fake_filter_for_netuids = filter_for_netuids + fake_all_hotkeys = [fake_wallet_1, fake_wallet_2] + fake_block_hash = "fake_block_hash" + fake_reuse_block = True + + mocked_get_netuids_for_hotkey = mocker.AsyncMock( + # returned subnets list + return_value=[1, 2] + ) + subtensor.get_netuids_for_hotkey = mocked_get_netuids_for_hotkey + + # Call + + result = await subtensor.filter_netuids_by_registered_hotkeys( + all_netuids=fake_all_netuids, + filter_for_netuids=fake_filter_for_netuids, + all_hotkeys=fake_all_hotkeys, + block_hash=fake_block_hash, + reuse_block=fake_reuse_block, + ) + + # Asserts + mocked_get_netuids_for_hotkey.call_count = len(fake_all_netuids) + assert mocked_get_netuids_for_hotkey.mock_calls == [ + mocker.call( + w.hotkey.ss58_address, + block_hash=fake_block_hash, + reuse_block=fake_reuse_block, + ) + for w in fake_all_hotkeys + ] + assert result == response + + +@pytest.mark.asyncio +async def test_get_existential_deposit_happy_path(subtensor, mocker): + """Tests get_existential_deposit method.""" + # Preps + fake_block_hash = "block_hash" + fake_reuse_block_hash = True + + mocked_substrate_get_constant = mocker.AsyncMock(return_value=1) + subtensor.substrate.get_constant = mocked_substrate_get_constant + + spy_balance_from_rao = mocker.spy(async_subtensor.Balance, "from_rao") + + # Call + result = await subtensor.get_existential_deposit( + block_hash=fake_block_hash, reuse_block=fake_reuse_block_hash + ) + + # Asserts + mocked_substrate_get_constant.assert_awaited_once() + mocked_substrate_get_constant.assert_called_once_with( + 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 + ) + assert result == async_subtensor.Balance(mocked_substrate_get_constant.return_value) + + +@pytest.mark.asyncio +async def test_get_existential_deposit_raise_exception(subtensor, mocker): + """Tests get_existential_deposit method raise Exception.""" + # Preps + fake_block_hash = "block_hash" + fake_reuse_block_hash = True + + mocked_substrate_get_constant = mocker.AsyncMock(return_value=None) + subtensor.substrate.get_constant = mocked_substrate_get_constant + + spy_balance_from_rao = mocker.spy(async_subtensor.Balance, "from_rao") + + # Call + with pytest.raises(Exception): + await subtensor.get_existential_deposit( + block_hash=fake_block_hash, reuse_block=fake_reuse_block_hash + ) + + # Asserts + mocked_substrate_get_constant.assert_awaited_once() + mocked_substrate_get_constant.assert_called_once_with( + 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() + + +@pytest.mark.asyncio +async def test_neurons(subtensor, mocker): + """Tests neurons method.""" + # Preps + fake_netuid = 1 + fake_block_hash = "block_hash" + fake_neurons = [mocker.Mock(), mocker.Mock()] + fake_weights = [(1, [(10, 20), (30, 40)]), (2, [(50, 60), (70, 80)])] + fake_bonds = [(1, [(10, 20), (30, 40)]), (2, [(50, 60), (70, 80)])] + + mocked_neurons_lite = mocker.AsyncMock(return_value=fake_neurons) + subtensor.neurons_lite = mocked_neurons_lite + + mocked_weights = mocker.AsyncMock(return_value=fake_weights) + subtensor.weights = mocked_weights + + mocked_bonds = mocker.AsyncMock(return_value=fake_bonds) + subtensor.bonds = mocked_bonds + + mocked_neuron_info_method = mocker.Mock() + async_subtensor.NeuronInfo.from_weights_bonds_and_neuron_lite = ( + mocked_neuron_info_method + ) + + # Call + result = await subtensor.neurons(netuid=fake_netuid, block_hash=fake_block_hash) + + # Asserts + mocked_neurons_lite.assert_awaited_once() + mocked_neurons_lite.assert_called_once_with( + netuid=fake_netuid, block_hash=fake_block_hash + ) + mocked_weights.assert_awaited_once() + mocked_weights.assert_called_once_with( + netuid=fake_netuid, block_hash=fake_block_hash + ) + mocked_bonds.assert_awaited_once() + mocked_bonds.assert_called_once_with(netuid=fake_netuid, block_hash=fake_block_hash) + assert result == [ + mocked_neuron_info_method.return_value for _ in range(len(fake_neurons)) + ] + + +@pytest.mark.parametrize( + "fake_hex_bytes_result, response", + [(None, []), ("0xaabbccdd", b"\xaa\xbb\xcc\xdd")], + ids=["none", "with data"], +) +@pytest.mark.asyncio +async def test_neurons_lite( + subtensor, mocker, fake_hex_bytes_result, response +): + """Tests neurons_lite method.""" + # Preps + fake_netuid = 1 + fake_block_hash = "block_hash" + fake_reuse_block_hash = True + + mocked_query_runtime_api = mocker.AsyncMock(return_value=fake_hex_bytes_result) + subtensor.query_runtime_api = mocked_query_runtime_api + + mocked_neuron_info_lite_list_from_vec_u8 = mocker.Mock() + async_subtensor.NeuronInfoLite.list_from_vec_u8 = ( + mocked_neuron_info_lite_list_from_vec_u8 + ) + + # Call + result = await subtensor.neurons_lite( + netuid=fake_netuid, + block_hash=fake_block_hash, + reuse_block=fake_reuse_block_hash, + ) + + # Assertions + mocked_query_runtime_api.assert_awaited_once() + mocked_query_runtime_api.assert_called_once_with( + runtime_api="NeuronInfoRuntimeApi", + method="get_neurons_lite", + params=[fake_netuid], + block_hash=fake_block_hash, + reuse_block=fake_reuse_block_hash, + ) + if fake_hex_bytes_result: + mocked_neuron_info_lite_list_from_vec_u8.assert_called_once_with( + bytes.fromhex(fake_hex_bytes_result[2:]) + ) + assert result == mocked_neuron_info_lite_list_from_vec_u8.return_value + else: + mocked_neuron_info_lite_list_from_vec_u8.assert_not_called() + assert result == [] From c17f2af5cc61d09b40a229d5e8847399887b46cc Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Nov 2024 17:03:30 -0800 Subject: [PATCH 2/7] ruff --- tests/unit_tests/test_async_subtensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index a34afb92a..a3afef7cc 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -924,9 +924,7 @@ async def test_neurons(subtensor, mocker): ids=["none", "with data"], ) @pytest.mark.asyncio -async def test_neurons_lite( - subtensor, mocker, fake_hex_bytes_result, response -): +async def test_neurons_lite(subtensor, mocker, fake_hex_bytes_result, response): """Tests neurons_lite method.""" # Preps fake_netuid = 1 From 2c964fd1cf5f7b3e0b0264b3b0cea49a003194a0 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Nov 2024 18:48:12 -0800 Subject: [PATCH 3/7] move out `_decode_hex_identity_dict` from inner function --- bittensor/core/async_subtensor.py | 53 ++++++++++++++++--------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index aa5cddc4a..c0d5328b9 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -82,6 +82,32 @@ def decode_ss58_tuples(line: tuple): return [decode_account_id(line[x][0]) for x in range(len(line))] +def _decode_hex_identity_dict(info_dictionary: dict[str, Any]) -> dict[str, Any]: + """Decodes a dictionary of hexadecimal identities.""" + for k, v in info_dictionary.items(): + if isinstance(v, dict): + item = next(iter(v.values())) + else: + item = v + if isinstance(item, tuple) and item: + if len(item) > 1: + try: + info_dictionary[k] = ( + bytes(item).hex(sep=" ", bytes_per_sep=2).upper() + ) + except UnicodeDecodeError: + print(f"Could not decode: {k}: {item}") + else: + try: + info_dictionary[k] = bytes(item[0]).decode("utf-8") + except UnicodeDecodeError: + print(f"Could not decode: {k}: {item}") + else: + info_dictionary[k] = item + + return info_dictionary + + class AsyncSubtensor: """Thin layer for interacting with Substrate Interface. Mostly a collection of frequently-used calls.""" @@ -858,7 +884,6 @@ async def neuron_for_uid( method="neuronInfo_getNeuron", params=params, # custom rpc method ) - if not (result := json_body.get("result", None)): return NeuronInfo.get_null_neuron() @@ -924,30 +949,6 @@ async def query_identity( See the `Bittensor CLI documentation `_ for supported identity parameters. """ - def decode_hex_identity_dict_(info_dictionary): - for k, v in info_dictionary.items(): - if isinstance(v, dict): - item = next(iter(v.values())) - else: - item = v - if isinstance(item, tuple) and item: - if len(item) > 1: - try: - info_dictionary[k] = ( - bytes(item).hex(sep=" ", bytes_per_sep=2).upper() - ) - except UnicodeDecodeError: - print(f"Could not decode: {k}: {item}") - else: - try: - info_dictionary[k] = bytes(item[0]).decode("utf-8") - except UnicodeDecodeError: - print(f"Could not decode: {k}: {item}") - else: - info_dictionary[k] = item - - return info_dictionary - identity_info = await self.substrate.query( module="Registry", storage_function="IdentityOf", @@ -956,7 +957,7 @@ def decode_hex_identity_dict_(info_dictionary): reuse_block_hash=reuse_block, ) try: - return decode_hex_identity_dict_(identity_info["info"]) + return _decode_hex_identity_dict(identity_info["info"]) except TypeError: return {} From 2f4c1abbadcf1409c4bb99d91b1666e7329db1dd Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Nov 2024 18:48:45 -0800 Subject: [PATCH 4/7] added tests until `AsyncSubtensor.query_identity` --- tests/unit_tests/test_async_subtensor.py | 312 +++++++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index a3afef7cc..1183ef76a 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -41,6 +41,36 @@ def test_decode_ss58_tuples_in_proposal_vote_data(mocker): ] +def test_decode_hex_identity_dict_with_single_byte_utf8(): + """Tests _decode_hex_identity_dict when value is a single utf-8 decodable byte.""" + info_dict = {"name": (b"Neuron",)} + result = async_subtensor._decode_hex_identity_dict(info_dict) + assert result["name"] == "Neuron" + + +def test_decode_hex_identity_dict_with_non_utf8_data(capfd): + """Tests _decode_hex_identity_dict when value cannot be decoded as utf-8.""" + info_dict = {"data": (b"\xff\xfe",)} + result = async_subtensor._decode_hex_identity_dict(info_dict) + captured = capfd.readouterr() + assert result["data"] == (b"\xff\xfe",) + assert "Could not decode: data: (b'\\xff\\xfe',)" in captured.out + + +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"} + result = async_subtensor._decode_hex_identity_dict(info_dict) + assert result["info"] == "regular_string" + + +def test_decode_hex_identity_dict_with_nested_dict(): + """Tests _decode_hex_identity_dict with a nested dictionary.""" + info_dict = {"identity": {"rank": (65, 66, 67)}} + result = async_subtensor._decode_hex_identity_dict(info_dict) + assert result["identity"] == "41 4243" + + def test__str__return(subtensor): """Simply tests the result if printing subtensor instance.""" # Asserts @@ -963,3 +993,285 @@ async def test_neurons_lite(subtensor, mocker, fake_hex_bytes_result, response): else: mocked_neuron_info_lite_list_from_vec_u8.assert_not_called() assert result == [] + + +@pytest.mark.asyncio +async def test_neuron_for_uid_happy_path(subtensor, mocker): + """Tests neuron_for_uid method with happy path.""" + # Preps + fake_uid = 1 + fake_netuid = 2 + fake_block_hash = "block_hash" + + mocked_null_neuron = mocker.Mock() + async_subtensor.NeuronInfo.get_null_neuron = mocked_null_neuron + + # no result in response + mocked_substrate_rpc_request = mocker.AsyncMock( + return_value={"result": b"some_result"} + ) + subtensor.substrate.rpc_request = mocked_substrate_rpc_request + + mocked_neuron_info_from_vec_u8 = mocker.Mock() + async_subtensor.NeuronInfo.from_vec_u8 = mocked_neuron_info_from_vec_u8 + + # Call + result = await subtensor.neuron_for_uid( + uid=fake_uid, netuid=fake_netuid, block_hash=fake_block_hash + ) + + # Asserts + mocked_null_neuron.assert_not_called() + mocked_neuron_info_from_vec_u8.assert_called_once_with( + bytes(mocked_substrate_rpc_request.return_value.get("result")) + ) + assert result == mocked_neuron_info_from_vec_u8.return_value + + +@pytest.mark.asyncio +async def test_neuron_for_uid_with_none_uid(subtensor, mocker): + """Tests neuron_for_uid method when uid is None.""" + # Preps + fake_uid = None + fake_netuid = 1 + fake_block_hash = "block_hash" + + mocked_null_neuron = mocker.Mock() + async_subtensor.NeuronInfo.get_null_neuron = mocked_null_neuron + + # Call + result = await subtensor.neuron_for_uid( + uid=fake_uid, netuid=fake_netuid, block_hash=fake_block_hash + ) + + # Asserts + mocked_null_neuron.assert_called_once() + assert result == mocked_null_neuron.return_value + + +@pytest.mark.asyncio +async def test_neuron_for_uid(subtensor, mocker): + """Tests neuron_for_uid method.""" + # Preps + fake_uid = 1 + fake_netuid = 2 + fake_block_hash = "block_hash" + + mocked_null_neuron = mocker.Mock() + async_subtensor.NeuronInfo.get_null_neuron = mocked_null_neuron + + # no result in response + mocked_substrate_rpc_request = mocker.AsyncMock(return_value={}) + subtensor.substrate.rpc_request = mocked_substrate_rpc_request + + mocked_neuron_info_from_vec_u8 = mocker.Mock() + async_subtensor.NeuronInfo.from_vec_u8 = mocked_neuron_info_from_vec_u8 + + # Call + result = await subtensor.neuron_for_uid( + uid=fake_uid, netuid=fake_netuid, block_hash=fake_block_hash + ) + + # Asserts + mocked_null_neuron.assert_called_once() + mocked_neuron_info_from_vec_u8.assert_not_called() + assert result == mocked_null_neuron.return_value + + +@pytest.mark.asyncio +async def test_get_delegated_no_block_hash_no_reuse(subtensor, mocker): + """Tests get_delegated method with no block_hash and reuse_block=False.""" + # Preps + fake_coldkey_ss58 = "fake_ss58_address" + + mocked_ss58_to_vec_u8 = mocker.Mock(return_value=b"encoded_coldkey") + mocker.patch("bittensor.core.async_subtensor.ss58_to_vec_u8", mocked_ss58_to_vec_u8) + + mocked_rpc_request = mocker.AsyncMock(return_value={"result": b"mocked_result"}) + subtensor.substrate.rpc_request = mocked_rpc_request + + mocked_delegated_list_from_vec_u8 = mocker.Mock() + async_subtensor.DelegateInfo.delegated_list_from_vec_u8 = ( + mocked_delegated_list_from_vec_u8 + ) + + # Call + result = await subtensor.get_delegated(coldkey_ss58=fake_coldkey_ss58) + + # Asserts + mocked_ss58_to_vec_u8.assert_called_once_with(fake_coldkey_ss58) + mocked_rpc_request.assert_called_once_with( + method="delegateInfo_getDelegated", params=[b"encoded_coldkey"] + ) + mocked_delegated_list_from_vec_u8.assert_called_once_with(b"mocked_result") + assert result == mocked_delegated_list_from_vec_u8.return_value + + +@pytest.mark.asyncio +async def test_get_delegated_with_block_hash(subtensor, mocker): + """Tests get_delegated method with specified block_hash.""" + # Preps + fake_coldkey_ss58 = "fake_ss58_address" + fake_block_hash = "fake_block_hash" + + mocked_ss58_to_vec_u8 = mocker.Mock(return_value=b"encoded_coldkey") + mocker.patch("bittensor.core.async_subtensor.ss58_to_vec_u8", mocked_ss58_to_vec_u8) + + mocked_rpc_request = mocker.AsyncMock(return_value={"result": b"mocked_result"}) + subtensor.substrate.rpc_request = mocked_rpc_request + + mocked_delegated_list_from_vec_u8 = mocker.Mock() + async_subtensor.DelegateInfo.delegated_list_from_vec_u8 = ( + mocked_delegated_list_from_vec_u8 + ) + + # Call + result = await subtensor.get_delegated( + coldkey_ss58=fake_coldkey_ss58, block_hash=fake_block_hash + ) + + # Asserts + mocked_ss58_to_vec_u8.assert_called_once_with(fake_coldkey_ss58) + mocked_rpc_request.assert_called_once_with( + method="delegateInfo_getDelegated", params=[fake_block_hash, b"encoded_coldkey"] + ) + mocked_delegated_list_from_vec_u8.assert_called_once_with(b"mocked_result") + assert result == mocked_delegated_list_from_vec_u8.return_value + + +@pytest.mark.asyncio +async def test_get_delegated_with_reuse_block(subtensor, mocker): + """Tests get_delegated method with reuse_block=True.""" + # Preps + fake_coldkey_ss58 = "fake_ss58_address" + subtensor.substrate.last_block_hash = "last_block_hash" + + mocked_ss58_to_vec_u8 = mocker.Mock(return_value=b"encoded_coldkey") + mocker.patch("bittensor.core.async_subtensor.ss58_to_vec_u8", mocked_ss58_to_vec_u8) + + mocked_rpc_request = mocker.AsyncMock(return_value={"result": b"mocked_result"}) + subtensor.substrate.rpc_request = mocked_rpc_request + + mocked_delegated_list_from_vec_u8 = mocker.Mock() + async_subtensor.DelegateInfo.delegated_list_from_vec_u8 = ( + mocked_delegated_list_from_vec_u8 + ) + + # Call + result = await subtensor.get_delegated( + coldkey_ss58=fake_coldkey_ss58, reuse_block=True + ) + + # Asserts + mocked_ss58_to_vec_u8.assert_called_once_with(fake_coldkey_ss58) + mocked_rpc_request.assert_called_once_with( + method="delegateInfo_getDelegated", + params=["last_block_hash", b"encoded_coldkey"], + ) + mocked_delegated_list_from_vec_u8.assert_called_once_with(b"mocked_result") + assert result == mocked_delegated_list_from_vec_u8.return_value + + +@pytest.mark.asyncio +async def test_get_delegated_with_empty_result(subtensor, mocker): + """Tests get_delegated method when RPC request returns an empty result.""" + # Preps + fake_coldkey_ss58 = "fake_ss58_address" + + mocked_ss58_to_vec_u8 = mocker.Mock(return_value=b"encoded_coldkey") + mocker.patch("bittensor.core.async_subtensor.ss58_to_vec_u8", mocked_ss58_to_vec_u8) + + mocked_rpc_request = mocker.AsyncMock(return_value={}) + subtensor.substrate.rpc_request = mocked_rpc_request + + # Call + result = await subtensor.get_delegated(coldkey_ss58=fake_coldkey_ss58) + + # Asserts + mocked_ss58_to_vec_u8.assert_called_once_with(fake_coldkey_ss58) + mocked_rpc_request.assert_called_once_with( + method="delegateInfo_getDelegated", params=[b"encoded_coldkey"] + ) + assert result == [] + + +@pytest.mark.asyncio +async def test_query_identity_successful(subtensor, mocker): + """Tests query_identity method with successful identity query.""" + # Preps + fake_key = "test_key" + fake_block_hash = "block_hash" + fake_identity_info = {"info": {"stake": (b"\x01\x02",)}} + + mocked_query = mocker.AsyncMock(return_value=fake_identity_info) + subtensor.substrate.query = mocked_query + + mocker.patch( + "bittensor.core.async_subtensor._decode_hex_identity_dict", + return_value={"stake": "01 02"}, + ) + + # Call + result = await subtensor.query_identity(key=fake_key, block_hash=fake_block_hash) + + # Asserts + mocked_query.assert_called_once_with( + module="Registry", + storage_function="IdentityOf", + params=[fake_key], + block_hash=fake_block_hash, + reuse_block_hash=False, + ) + assert result == {"stake": "01 02"} + + +@pytest.mark.asyncio +async def test_query_identity_no_info(subtensor, mocker): + """Tests query_identity method when no identity info is returned.""" + # Preps + fake_key = "test_key" + + mocked_query = mocker.AsyncMock(return_value=None) + subtensor.substrate.query = mocked_query + + # Call + result = await subtensor.query_identity(key=fake_key) + + # Asserts + mocked_query.assert_called_once_with( + module="Registry", + storage_function="IdentityOf", + params=[fake_key], + block_hash=None, + reuse_block_hash=False, + ) + assert result == {} + + +@pytest.mark.asyncio +async def test_query_identity_type_error(subtensor, mocker): + """Tests query_identity method when a TypeError occurs during decoding.""" + # Preps + fake_key = "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( + "bittensor.core.async_subtensor._decode_hex_identity_dict", + side_effect=TypeError, + ) + + # Call + result = await subtensor.query_identity(key=fake_key) + + # Asserts + mocked_query.assert_called_once_with( + module="Registry", + storage_function="IdentityOf", + params=[fake_key], + block_hash=None, + reuse_block_hash=False, + ) + assert result == {} From d4209ec9cc4abc19cd35e22a76be39eea24b0c84 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Nov 2024 16:55:12 -0800 Subject: [PATCH 5/7] added tests from `AsyncSubtensor.get_netuids_for_hotkey` until `AsyncSubtensor.neurons_lite` --- tests/unit_tests/test_async_subtensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 1183ef76a..4b2ec932e 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -954,7 +954,9 @@ async def test_neurons(subtensor, mocker): ids=["none", "with data"], ) @pytest.mark.asyncio -async def test_neurons_lite(subtensor, mocker, fake_hex_bytes_result, response): +async def test_neurons_lite( + subtensor, mocker, fake_hex_bytes_result, response +): """Tests neurons_lite method.""" # Preps fake_netuid = 1 From b4837c52b76ad0a49ae2e3794e003404c5ed20d0 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 13 Nov 2024 08:50:38 -0800 Subject: [PATCH 6/7] fix --- bittensor/core/async_subtensor.py | 4 ++-- tests/unit_tests/test_async_subtensor.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index c0d5328b9..a9ba438d5 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -96,12 +96,12 @@ def _decode_hex_identity_dict(info_dictionary: dict[str, Any]) -> dict[str, Any] bytes(item).hex(sep=" ", bytes_per_sep=2).upper() ) except UnicodeDecodeError: - print(f"Could not decode: {k}: {item}") + logging.error(f"Could not decode: {k}: {item}.") else: try: info_dictionary[k] = bytes(item[0]).decode("utf-8") except UnicodeDecodeError: - print(f"Could not decode: {k}: {item}") + logging.error(f"Could not decode: {k}: {item}.") else: info_dictionary[k] = item diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 4b2ec932e..9a0e918bc 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -48,13 +48,11 @@ def test_decode_hex_identity_dict_with_single_byte_utf8(): assert result["name"] == "Neuron" -def test_decode_hex_identity_dict_with_non_utf8_data(capfd): +def test_decode_hex_identity_dict_with_non_utf8_data(): """Tests _decode_hex_identity_dict when value cannot be decoded as utf-8.""" info_dict = {"data": (b"\xff\xfe",)} result = async_subtensor._decode_hex_identity_dict(info_dict) - captured = capfd.readouterr() assert result["data"] == (b"\xff\xfe",) - assert "Could not decode: data: (b'\\xff\\xfe',)" in captured.out def test_decode_hex_identity_dict_with_non_tuple_value(): From c67f448ec4937ef46f3ec5a090324fc13f0747d2 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 13 Nov 2024 08:50:56 -0800 Subject: [PATCH 7/7] ruff --- tests/unit_tests/test_async_subtensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 9a0e918bc..ff7446aee 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -952,9 +952,7 @@ async def test_neurons(subtensor, mocker): ids=["none", "with data"], ) @pytest.mark.asyncio -async def test_neurons_lite( - subtensor, mocker, fake_hex_bytes_result, response -): +async def test_neurons_lite(subtensor, mocker, fake_hex_bytes_result, response): """Tests neurons_lite method.""" # Preps fake_netuid = 1