Skip to content

Commit 62b2aa7

Browse files
authored
Merge pull request #2854 from opentensor/feat/roman/add-methods
Add methods to Async/Subtensor class
2 parents 43226b5 + 7288c8f commit 62b2aa7

File tree

4 files changed

+384
-0
lines changed

4 files changed

+384
-0
lines changed

bittensor/core/async_subtensor.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,23 @@ async def all_subnets(
580580
subnets = DynamicInfo.list_from_dicts(query.decode())
581581
return subnets
582582

583+
async def blocks_since_last_step(
584+
self, netuid: int, block: Optional[int] = None
585+
) -> Optional[int]:
586+
"""Returns number of blocks since the last epoch of the subnet.
587+
588+
Arguments:
589+
netuid (int): The unique identifier of the subnetwork.
590+
block: the block number for this query.
591+
592+
Returns:
593+
block number of the last step in the subnet.
594+
"""
595+
query = await self.query_subtensor(
596+
name="BlocksSinceLastStep", block=block, params=[netuid]
597+
)
598+
return query.value if query is not None and hasattr(query, "value") else query
599+
583600
async def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]:
584601
"""
585602
Returns the number of blocks since the last update for a specific UID in the subnetwork.
@@ -3149,6 +3166,46 @@ async def get_timestamp(
31493166
).value
31503167
return datetime.fromtimestamp(unix / 1000, tz=timezone.utc)
31513168

3169+
async def get_subnet_owner_hotkey(
3170+
self, netuid: int, block: Optional[int] = None
3171+
) -> Optional[str]:
3172+
"""
3173+
Retrieves the hotkey of the subnet owner for a given network UID.
3174+
3175+
This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its
3176+
netuid. If no data is found or the query fails, the function returns None.
3177+
3178+
Arguments:
3179+
netuid: The network UID of the subnet to fetch the owner's hotkey for.
3180+
block: The specific block number to query the data from.
3181+
3182+
Returns:
3183+
The hotkey of the subnet owner if available; None otherwise.
3184+
"""
3185+
return await self.query_subtensor(
3186+
name="SubnetOwnerHotkey", params=[netuid], block=block
3187+
)
3188+
3189+
async def get_subnet_validator_permits(
3190+
self, netuid: int, block: Optional[int] = None
3191+
) -> Optional[list[bool]]:
3192+
"""
3193+
Retrieves the list of validator permits for a given subnet as boolean values.
3194+
3195+
Arguments:
3196+
netuid: The unique identifier of the subnetwork.
3197+
block: The blockchain block number for the query.
3198+
3199+
Returns:
3200+
A list of boolean values representing validator permits, or None if not available.
3201+
"""
3202+
query = await self.query_subtensor(
3203+
name="ValidatorPermit",
3204+
params=[netuid],
3205+
block=block,
3206+
)
3207+
return query.value if query is not None and hasattr(query, "value") else query
3208+
31523209
# Extrinsics helper ================================================================================================
31533210

31543211
async def sign_and_send_extrinsic(
@@ -3172,6 +3229,9 @@ async def sign_and_send_extrinsic(
31723229
wait_for_inclusion (bool): whether to wait until the extrinsic call is included on the chain
31733230
wait_for_finalization (bool): whether to wait until the extrinsic call is finalized on the chain
31743231
sign_with: the wallet's keypair to use for the signing. Options are "coldkey", "hotkey", "coldkeypub"
3232+
use_nonce: unique identifier for the transaction related with hot/coldkey.
3233+
period: the period of the transaction as ERA part for transaction. Means how many blocks the transaction will be valid for.
3234+
nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey".
31753235
raise_error: raises relevant exception rather than returning `False` if unsuccessful.
31763236
31773237
Returns:

bittensor/core/subtensor.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,23 @@ def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo
388388
)
389389
return DynamicInfo.list_from_dicts(query.decode())
390390

391+
def blocks_since_last_step(
392+
self, netuid: int, block: Optional[int] = None
393+
) -> Optional[int]:
394+
"""Returns number of blocks since the last epoch of the subnet.
395+
396+
Arguments:
397+
netuid (int): The unique identifier of the subnetwork.
398+
block: the block number for this query.
399+
400+
Returns:
401+
block number of the last step in the subnet.
402+
"""
403+
query = self.query_subtensor(
404+
name="BlocksSinceLastStep", block=block, params=[netuid]
405+
)
406+
return query.value if query is not None and hasattr(query, "value") else query
407+
391408
def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]:
392409
"""
393410
Returns the number of blocks since the last update for a specific UID in the subnetwork.
@@ -2575,6 +2592,46 @@ def get_timestamp(self, block: Optional[int] = None) -> datetime:
25752592
unix = cast(ScaleObj, self.query_module("Timestamp", "Now", block=block)).value
25762593
return datetime.fromtimestamp(unix / 1000, tz=timezone.utc)
25772594

2595+
def get_subnet_owner_hotkey(
2596+
self, netuid: int, block: Optional[int] = None
2597+
) -> Optional[str]:
2598+
"""
2599+
Retrieves the hotkey of the subnet owner for a given network UID.
2600+
2601+
This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its
2602+
netuid. If no data is found or the query fails, the function returns None.
2603+
2604+
Arguments:
2605+
netuid: The network UID of the subnet to fetch the owner's hotkey for.
2606+
block: The specific block number to query the data from.
2607+
2608+
Returns:
2609+
The hotkey of the subnet owner if available; None otherwise.
2610+
"""
2611+
return self.query_subtensor(
2612+
name="SubnetOwnerHotkey", params=[netuid], block=block
2613+
)
2614+
2615+
def get_subnet_validator_permits(
2616+
self, netuid: int, block: Optional[int] = None
2617+
) -> Optional[list[bool]]:
2618+
"""
2619+
Retrieves the list of validator permits for a given subnet as boolean values.
2620+
2621+
Arguments:
2622+
netuid: The unique identifier of the subnetwork.
2623+
block: The blockchain block number for the query.
2624+
2625+
Returns:
2626+
A list of boolean values representing validator permits, or None if not available.
2627+
"""
2628+
query = self.query_subtensor(
2629+
name="ValidatorPermit",
2630+
params=[netuid],
2631+
block=block,
2632+
)
2633+
return query.value if query is not None and hasattr(query, "value") else query
2634+
25782635
# Extrinsics helper ================================================================================================
25792636

25802637
def sign_and_send_extrinsic(
@@ -2598,6 +2655,9 @@ def sign_and_send_extrinsic(
25982655
wait_for_inclusion (bool): whether to wait until the extrinsic call is included on the chain
25992656
wait_for_finalization (bool): whether to wait until the extrinsic call is finalized on the chain
26002657
sign_with: the wallet's keypair to use for the signing. Options are "coldkey", "hotkey", "coldkeypub"
2658+
use_nonce: unique identifier for the transaction related with hot/coldkey.
2659+
period: the period of the transaction as ERA part for transaction. Means how many blocks the transaction will be valid for.
2660+
nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey".
26012661
raise_error: raises relevant exception rather than returning `False` if unsuccessful.
26022662
26032663
Returns:

tests/unit_tests/test_async_subtensor.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3168,3 +3168,138 @@ async def test_get_metagraph_info_subnet_not_exist(subtensor, mocker):
31683168

31693169
assert result is None
31703170
mocked_logger.assert_called_once_with(f"Subnet {netuid} does not exist.")
3171+
3172+
3173+
@pytest.mark.asyncio
3174+
async def test_blocks_since_last_step_with_value(subtensor, mocker):
3175+
"""Test blocks_since_last_step returns correct value."""
3176+
# preps
3177+
netuid = 1
3178+
block = 123
3179+
mocked_query_subtensor = mocker.AsyncMock()
3180+
subtensor.query_subtensor = mocked_query_subtensor
3181+
3182+
# call
3183+
result = await subtensor.blocks_since_last_step(netuid=netuid, block=block)
3184+
3185+
# asserts
3186+
mocked_query_subtensor.assert_awaited_once_with(
3187+
name="BlocksSinceLastStep",
3188+
block=block,
3189+
params=[netuid],
3190+
)
3191+
3192+
assert result == mocked_query_subtensor.return_value.value
3193+
3194+
3195+
@pytest.mark.asyncio
3196+
async def test_blocks_since_last_step_is_none(subtensor, mocker):
3197+
"""Test blocks_since_last_step returns None correctly."""
3198+
# preps
3199+
netuid = 1
3200+
block = 123
3201+
mocked_query_subtensor = mocker.AsyncMock(return_value=None)
3202+
subtensor.query_subtensor = mocked_query_subtensor
3203+
3204+
# call
3205+
result = await subtensor.blocks_since_last_step(netuid=netuid, block=block)
3206+
3207+
# asserts
3208+
mocked_query_subtensor.assert_awaited_once_with(
3209+
name="BlocksSinceLastStep",
3210+
block=block,
3211+
params=[netuid],
3212+
)
3213+
3214+
assert result is None
3215+
3216+
3217+
@pytest.mark.asyncio
3218+
async def test_get_subnet_owner_hotkey_has_return(subtensor, mocker):
3219+
"""Test get_subnet_owner_hotkey returns correct value."""
3220+
# preps
3221+
netuid = 14
3222+
block = 123
3223+
expected_owner_hotkey = "owner_hotkey"
3224+
mocked_query_subtensor = mocker.AsyncMock(return_value=expected_owner_hotkey)
3225+
subtensor.query_subtensor = mocked_query_subtensor
3226+
3227+
# call
3228+
result = await subtensor.get_subnet_owner_hotkey(netuid=netuid, block=block)
3229+
3230+
# asserts
3231+
mocked_query_subtensor.assert_awaited_once_with(
3232+
name="SubnetOwnerHotkey",
3233+
block=block,
3234+
params=[netuid],
3235+
)
3236+
3237+
assert result == expected_owner_hotkey
3238+
3239+
3240+
@pytest.mark.asyncio
3241+
async def test_get_subnet_owner_hotkey_is_none(subtensor, mocker):
3242+
"""Test get_subnet_owner_hotkey returns None correctly."""
3243+
# preps
3244+
netuid = 14
3245+
block = 123
3246+
mocked_query_subtensor = mocker.AsyncMock(return_value=None)
3247+
subtensor.query_subtensor = mocked_query_subtensor
3248+
3249+
# call
3250+
result = await subtensor.get_subnet_owner_hotkey(netuid=netuid, block=block)
3251+
3252+
# asserts
3253+
mocked_query_subtensor.assert_awaited_once_with(
3254+
name="SubnetOwnerHotkey",
3255+
block=block,
3256+
params=[netuid],
3257+
)
3258+
3259+
assert result is None
3260+
3261+
3262+
@pytest.mark.asyncio
3263+
async def test_get_subnet_validator_permits_has_values(subtensor, mocker):
3264+
"""Test get_subnet_validator_permits returns correct value."""
3265+
# preps
3266+
netuid = 14
3267+
block = 123
3268+
expected_validator_permits = [False, True, False]
3269+
mocked_query_subtensor = mocker.AsyncMock(return_value=expected_validator_permits)
3270+
subtensor.query_subtensor = mocked_query_subtensor
3271+
3272+
# call
3273+
result = await subtensor.get_subnet_validator_permits(netuid=netuid, block=block)
3274+
3275+
# asserts
3276+
mocked_query_subtensor.assert_awaited_once_with(
3277+
name="ValidatorPermit",
3278+
block=block,
3279+
params=[netuid],
3280+
)
3281+
3282+
assert result == expected_validator_permits
3283+
3284+
3285+
@pytest.mark.asyncio
3286+
async def test_get_subnet_validator_permits_is_none(subtensor, mocker):
3287+
"""Test get_subnet_validator_permits returns correct value."""
3288+
# preps
3289+
netuid = 14
3290+
block = 123
3291+
3292+
mocked_query_subtensor = mocker.AsyncMock(return_value=None)
3293+
subtensor.query_subtensor = mocked_query_subtensor
3294+
3295+
# call
3296+
result = await subtensor.get_subnet_validator_permits(netuid=netuid, block=block)
3297+
3298+
# asserts
3299+
mocked_query_subtensor.assert_awaited_once_with(
3300+
name="ValidatorPermit",
3301+
block=block,
3302+
params=[netuid],
3303+
)
3304+
3305+
assert result is None

0 commit comments

Comments
 (0)