Skip to content

Commit 07a62f3

Browse files
authored
Merge pull request #2766 from opentensor/feat/roman/add-get-owned-hotkeys
Add get_owned_hotkeys to subtensor and async one + tests
2 parents 177ed99 + 973a2b6 commit 07a62f3

File tree

4 files changed

+167
-0
lines changed

4 files changed

+167
-0
lines changed

bittensor/core/async_subtensor.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1572,6 +1572,36 @@ async def get_neuron_for_pubkey_and_subnet(
15721572
reuse_block=reuse_block,
15731573
)
15741574

1575+
async def get_owned_hotkeys(
1576+
self,
1577+
coldkey_ss58: str,
1578+
block: Optional[int] = None,
1579+
block_hash: Optional[str] = None,
1580+
reuse_block: bool = False,
1581+
) -> list[str]:
1582+
"""
1583+
Retrieves all hotkeys owned by a specific coldkey address.
1584+
1585+
Args:
1586+
coldkey_ss58 (str): The SS58 address of the coldkey to query.
1587+
block (int): The blockchain block number for the query.
1588+
block_hash (str): The hash of the blockchain block number for the query.
1589+
reuse_block (bool): Whether to reuse the last-used blockchain block hash.
1590+
1591+
Returns:
1592+
list[str]: A list of hotkey SS58 addresses owned by the coldkey.
1593+
"""
1594+
block_hash = await self.determine_block_hash(block, block_hash, reuse_block)
1595+
owned_hotkeys = await self.substrate.query(
1596+
module="SubtensorModule",
1597+
storage_function="OwnedHotkeys",
1598+
params=[coldkey_ss58],
1599+
block_hash=block_hash,
1600+
reuse_block_hash=reuse_block,
1601+
)
1602+
1603+
return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
1604+
15751605
async def get_stake(
15761606
self,
15771607
coldkey_ss58: str,

bittensor/core/subtensor.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,33 @@ def get_neuron_for_pubkey_and_subnet(
12001200

12011201
return NeuronInfo.from_dict(result)
12021202

1203+
def get_owned_hotkeys(
1204+
self,
1205+
coldkey_ss58: str,
1206+
block: Optional[int] = None,
1207+
reuse_block: bool = False,
1208+
) -> list[str]:
1209+
"""
1210+
Retrieves all hotkeys owned by a specific coldkey address.
1211+
1212+
Args:
1213+
coldkey_ss58 (str): The SS58 address of the coldkey to query.
1214+
block (int): The blockchain block number for the query.
1215+
reuse_block (bool): Whether to reuse the last-used blockchain block hash.
1216+
1217+
Returns:
1218+
list[str]: A list of hotkey SS58 addresses owned by the coldkey.
1219+
"""
1220+
block_hash = self.determine_block_hash(block)
1221+
owned_hotkeys = self.substrate.query(
1222+
module="SubtensorModule",
1223+
storage_function="OwnedHotkeys",
1224+
params=[coldkey_ss58],
1225+
block_hash=block_hash,
1226+
reuse_block_hash=reuse_block,
1227+
)
1228+
return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
1229+
12031230
def get_stake(
12041231
self,
12051232
coldkey_ss58: str,

tests/unit_tests/test_async_subtensor.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2956,3 +2956,59 @@ async def test_get_timestamp(mocker, subtensor):
29562956
)
29572957
actual_result = await subtensor.get_timestamp(block=fake_block)
29582958
assert expected_result == actual_result
2959+
2960+
2961+
@pytest.mark.asyncio
2962+
async def test_get_owned_hotkeys_happy_path(subtensor, mocker):
2963+
"""Tests that the output of get_owned_hotkeys."""
2964+
# Prep
2965+
fake_coldkey = "fake_hotkey"
2966+
fake_hotkey = "fake_hotkey"
2967+
fake_hotkeys = [
2968+
[
2969+
fake_hotkey,
2970+
]
2971+
]
2972+
mocked_subtensor = mocker.AsyncMock(return_value=fake_hotkeys)
2973+
mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor)
2974+
2975+
mocked_decode_account_id = mocker.Mock()
2976+
mocker.patch.object(
2977+
async_subtensor, "decode_account_id", new=mocked_decode_account_id
2978+
)
2979+
2980+
# Call
2981+
result = await subtensor.get_owned_hotkeys(fake_coldkey)
2982+
2983+
# Asserts
2984+
mocked_subtensor.assert_awaited_once_with(
2985+
module="SubtensorModule",
2986+
storage_function="OwnedHotkeys",
2987+
params=[fake_coldkey],
2988+
block_hash=None,
2989+
reuse_block_hash=False,
2990+
)
2991+
assert result == [mocked_decode_account_id.return_value]
2992+
mocked_decode_account_id.assert_called_once_with(fake_hotkey)
2993+
2994+
2995+
@pytest.mark.asyncio
2996+
async def test_get_owned_hotkeys_return_empty(subtensor, mocker):
2997+
"""Tests that the output of get_owned_hotkeys is empty."""
2998+
# Prep
2999+
fake_coldkey = "fake_hotkey"
3000+
mocked_subtensor = mocker.AsyncMock(return_value=[])
3001+
mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor)
3002+
3003+
# Call
3004+
result = await subtensor.get_owned_hotkeys(fake_coldkey)
3005+
3006+
# Asserts
3007+
mocked_subtensor.assert_awaited_once_with(
3008+
module="SubtensorModule",
3009+
storage_function="OwnedHotkeys",
3010+
params=[fake_coldkey],
3011+
block_hash=None,
3012+
reuse_block_hash=False,
3013+
)
3014+
assert result == []

tests/unit_tests/test_subtensor.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3332,3 +3332,57 @@ def test_stake_fee_methods(mocker, subtensor):
33323332
],
33333333
block=None,
33343334
)
3335+
3336+
3337+
def test_get_owned_hotkeys_happy_path(subtensor, mocker):
3338+
"""Tests that the output of get_owned_hotkeys."""
3339+
# Prep
3340+
fake_coldkey = "fake_hotkey"
3341+
fake_hotkey = "fake_hotkey"
3342+
fake_hotkeys = [
3343+
[
3344+
fake_hotkey,
3345+
]
3346+
]
3347+
mocked_subtensor = mocker.Mock(return_value=fake_hotkeys)
3348+
mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor)
3349+
3350+
mocked_decode_account_id = mocker.Mock()
3351+
mocker.patch.object(
3352+
subtensor_module, "decode_account_id", new=mocked_decode_account_id
3353+
)
3354+
3355+
# Call
3356+
result = subtensor.get_owned_hotkeys(fake_coldkey)
3357+
3358+
# Asserts
3359+
mocked_subtensor.assert_called_once_with(
3360+
module="SubtensorModule",
3361+
storage_function="OwnedHotkeys",
3362+
params=[fake_coldkey],
3363+
block_hash=None,
3364+
reuse_block_hash=False,
3365+
)
3366+
assert result == [mocked_decode_account_id.return_value]
3367+
mocked_decode_account_id.assert_called_once_with(fake_hotkey)
3368+
3369+
3370+
def test_get_owned_hotkeys_return_empty(subtensor, mocker):
3371+
"""Tests that the output of get_owned_hotkeys is empty."""
3372+
# Prep
3373+
fake_coldkey = "fake_hotkey"
3374+
mocked_subtensor = mocker.Mock(return_value=[])
3375+
mocker.patch.object(subtensor.substrate, "query", new=mocked_subtensor)
3376+
3377+
# Call
3378+
result = subtensor.get_owned_hotkeys(fake_coldkey)
3379+
3380+
# Asserts
3381+
mocked_subtensor.assert_called_once_with(
3382+
module="SubtensorModule",
3383+
storage_function="OwnedHotkeys",
3384+
params=[fake_coldkey],
3385+
block_hash=None,
3386+
reuse_block_hash=False,
3387+
)
3388+
assert result == []

0 commit comments

Comments
 (0)