Skip to content

Commit 23f25a3

Browse files
authored
Merge branch 'staging' into fix-pr-template
2 parents e301a3b + 6219525 commit 23f25a3

File tree

7 files changed

+257
-11
lines changed

7 files changed

+257
-11
lines changed

.github/workflows/e2e-subtensor-tests.yaml

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,31 @@ jobs:
128128
fi
129129
done
130130
131+
# run non-fast-blocks only on Saturday and by cron schedule
132+
check-if-saturday:
133+
if: github.event_name == 'schedule'
134+
runs-on: ubuntu-latest
135+
outputs:
136+
is-saturday: ${{ steps.check.outputs.is-saturday }}
137+
steps:
138+
- id: check
139+
run: |
140+
day=$(date -u +%u)
141+
echo "Today is weekday $day"
142+
if [ "$day" -ne 6 ]; then
143+
echo "⏭️ Skipping: not Saturday"
144+
echo "is-saturday=false" >> "$GITHUB_OUTPUT"
145+
exit 0
146+
fi
147+
echo "is-saturday=true"
148+
echo "is-saturday=true" >> "$GITHUB_OUTPUT"
149+
131150
132151
cron-run-non-fast-blocks-e2e-test:
133-
if: github.event_name == 'schedule'
152+
if: github.event_name == 'schedule' && needs.check-if-saturday.outputs.is-saturday == 'true'
134153
name: "NFB: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}"
135154
needs:
155+
- check-if-saturday
136156
- find-tests
137157
- pull-docker-image
138158
runs-on: ubuntu-latest
@@ -148,14 +168,6 @@ jobs:
148168
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
149169

150170
steps:
151-
- name: Check if today is Saturday
152-
run: |
153-
day=$(date -u +%u)
154-
echo "Today is weekday $day"
155-
if [ "$day" -ne 6 ]; then
156-
echo "⏭️ Skipping: not Saturday"
157-
exit 78
158-
fi
159171
- name: Check-out repository
160172
uses: actions/checkout@v4
161173

bittensor/core/async_subtensor.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1796,7 +1796,7 @@ async def get_next_epoch_start_block(
17961796
netuid=netuid, block=block, block_hash=block_hash, reuse_block=reuse_block
17971797
)
17981798

1799-
if block and blocks_since_last_step and tempo:
1799+
if block and blocks_since_last_step is not None and tempo:
18001800
return block - blocks_since_last_step + tempo + 1
18011801
return None
18021802

@@ -1921,6 +1921,42 @@ async def get_stake_add_fee(
19211921
)
19221922
return Balance.from_rao(result)
19231923

1924+
async def get_subnet_info(
1925+
self,
1926+
netuid: int,
1927+
block: Optional[int] = None,
1928+
block_hash: Optional[str] = None,
1929+
reuse_block: bool = False,
1930+
) -> Optional["SubnetInfo"]:
1931+
"""
1932+
Retrieves detailed information about subnet within the Bittensor network.
1933+
This function provides comprehensive data on subnet, including its characteristics and operational parameters.
1934+
1935+
Arguments:
1936+
netuid: The unique identifier of the subnet.
1937+
block: The blockchain block number for the query.
1938+
block_hash (Optional[str]): The hash of the block to retrieve the stake from. Do not specify if using block
1939+
or reuse_block
1940+
reuse_block (bool): Whether to use the last-used block. Do not set if using block_hash or block.
1941+
1942+
Returns:
1943+
SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet.
1944+
1945+
Gaining insights into the subnet's details assists in understanding the network's composition, the roles of
1946+
different subnets, and their unique features.
1947+
"""
1948+
result = await self.query_runtime_api(
1949+
runtime_api="SubnetInfoRuntimeApi",
1950+
method="get_subnet_info_v2",
1951+
params=[netuid],
1952+
block=block,
1953+
block_hash=block_hash,
1954+
reuse_block=reuse_block,
1955+
)
1956+
if not result:
1957+
return None
1958+
return SubnetInfo.from_dict(result)
1959+
19241960
async def get_unstake_fee(
19251961
self,
19261962
amount: Balance,

bittensor/core/subtensor.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1385,7 +1385,7 @@ def get_next_epoch_start_block(
13851385
blocks_since_last_step = self.blocks_since_last_step(netuid=netuid, block=block)
13861386
tempo = self.tempo(netuid=netuid, block=block)
13871387

1388-
if block and blocks_since_last_step and tempo:
1388+
if block and blocks_since_last_step is not None and tempo:
13891389
return block - blocks_since_last_step + tempo + 1
13901390
return None
13911391

@@ -1507,6 +1507,33 @@ def get_stake_add_fee(
15071507
)
15081508
return Balance.from_rao(result)
15091509

1510+
def get_subnet_info(
1511+
self, netuid: int, block: Optional[int] = None
1512+
) -> Optional["SubnetInfo"]:
1513+
"""
1514+
Retrieves detailed information about subnet within the Bittensor network.
1515+
This function provides comprehensive data on subnet, including its characteristics and operational parameters.
1516+
1517+
Arguments:
1518+
netuid: The unique identifier of the subnet.
1519+
block: The blockchain block number for the query.
1520+
1521+
Returns:
1522+
SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet.
1523+
1524+
Gaining insights into the subnet's details assists in understanding the network's composition, the roles of
1525+
different subnets, and their unique features.
1526+
"""
1527+
result = self.query_runtime_api(
1528+
runtime_api="SubnetInfoRuntimeApi",
1529+
method="get_subnet_info_v2",
1530+
params=[netuid],
1531+
block=block,
1532+
)
1533+
if not result:
1534+
return None
1535+
return SubnetInfo.from_dict(result)
1536+
15101537
def get_unstake_fee(
15111538
self,
15121539
amount: Balance,

bittensor/core/subtensor_api/subnets.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]):
2424
self.get_next_epoch_start_block = subtensor.get_next_epoch_start_block
2525
self.get_subnet_burn_cost = subtensor.get_subnet_burn_cost
2626
self.get_subnet_hyperparameters = subtensor.get_subnet_hyperparameters
27+
self.get_subnet_info = subtensor.get_subnet_info
2728
self.get_subnet_owner_hotkey = subtensor.get_subnet_owner_hotkey
2829
self.get_subnet_reveal_period_epochs = subtensor.get_subnet_reveal_period_epochs
2930
self.get_subnet_validator_permits = subtensor.get_subnet_validator_permits

bittensor/core/subtensor_api/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"):
8484
subtensor.get_subnet_hyperparameters = (
8585
subtensor._subtensor.get_subnet_hyperparameters
8686
)
87+
subtensor.get_subnet_info = subtensor._subtensor.get_subnet_info
8788
subtensor.get_subnet_owner_hotkey = subtensor._subtensor.get_subnet_owner_hotkey
8889
subtensor.get_subnet_reveal_period_epochs = (
8990
subtensor._subtensor.get_subnet_reveal_period_epochs

tests/unit_tests/test_async_subtensor.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3314,3 +3314,94 @@ async def test_get_subnet_validator_permits_is_none(subtensor, mocker):
33143314
)
33153315

33163316
assert result is None
3317+
3318+
3319+
@pytest.mark.asyncio
3320+
async def test_get_subnet_info_success(mocker, subtensor):
3321+
"""Test get_subnet_info returns correct data when subnet information is found."""
3322+
# Prep
3323+
netuid = mocker.Mock()
3324+
block = mocker.Mock()
3325+
3326+
mocker.patch.object(subtensor, "query_runtime_api")
3327+
mocker.patch.object(
3328+
async_subtensor.SubnetInfo,
3329+
"from_dict",
3330+
)
3331+
3332+
# Call
3333+
result = await subtensor.get_subnet_info(netuid=netuid, block=block)
3334+
3335+
# Asserts
3336+
subtensor.query_runtime_api.assert_awaited_once_with(
3337+
runtime_api="SubnetInfoRuntimeApi",
3338+
method="get_subnet_info_v2",
3339+
params=[netuid],
3340+
block=block,
3341+
block_hash=None,
3342+
reuse_block=False,
3343+
)
3344+
async_subtensor.SubnetInfo.from_dict.assert_called_once_with(
3345+
subtensor.query_runtime_api.return_value,
3346+
)
3347+
assert result == async_subtensor.SubnetInfo.from_dict.return_value
3348+
3349+
3350+
@pytest.mark.asyncio
3351+
async def test_get_subnet_info_no_data(mocker, subtensor):
3352+
"""Test get_subnet_info returns None."""
3353+
# Prep
3354+
netuid = mocker.Mock()
3355+
block = mocker.Mock()
3356+
mocker.patch.object(async_subtensor.SubnetInfo, "from_dict")
3357+
mocker.patch.object(subtensor, "query_runtime_api", return_value=None)
3358+
3359+
# Call
3360+
result = await subtensor.get_subnet_info(netuid=netuid, block=block)
3361+
3362+
# Asserts
3363+
subtensor.query_runtime_api.assert_awaited_once_with(
3364+
runtime_api="SubnetInfoRuntimeApi",
3365+
method="get_subnet_info_v2",
3366+
params=[netuid],
3367+
block=block,
3368+
block_hash=None,
3369+
reuse_block=False,
3370+
)
3371+
async_subtensor.SubnetInfo.from_dict.assert_not_called()
3372+
assert result is None
3373+
3374+
3375+
@pytest.mark.parametrize(
3376+
"call_return, expected",
3377+
[[10, 111], [None, None], [0, 121]],
3378+
)
3379+
@pytest.mark.asyncio
3380+
async def test_get_next_epoch_start_block(mocker, subtensor, call_return, expected):
3381+
"""Check that get_next_epoch_start_block returns the correct value."""
3382+
# Prep
3383+
netuid = mocker.Mock()
3384+
block = 20
3385+
3386+
fake_block_hash = mocker.Mock()
3387+
mocker.patch.object(subtensor, "get_block_hash", return_value=fake_block_hash)
3388+
3389+
mocked_blocks_since_last_step = mocker.AsyncMock(return_value=call_return)
3390+
subtensor.blocks_since_last_step = mocked_blocks_since_last_step
3391+
3392+
mocker.patch.object(subtensor, "tempo", return_value=100)
3393+
3394+
# Call
3395+
result = await subtensor.get_next_epoch_start_block(netuid=netuid, block=block)
3396+
3397+
# Asserts
3398+
mocked_blocks_since_last_step.assert_called_once_with(
3399+
netuid=netuid,
3400+
block=block,
3401+
block_hash=fake_block_hash,
3402+
reuse_block=False,
3403+
)
3404+
subtensor.tempo.assert_awaited_once_with(
3405+
netuid=netuid, block=block, block_hash=fake_block_hash, reuse_block=False
3406+
)
3407+
assert result == expected

tests/unit_tests/test_subtensor.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3668,3 +3668,81 @@ def test_is_subnet_active(subtensor, mocker, query_return, expected):
36683668
)
36693669

36703670
assert result == expected
3671+
3672+
3673+
# `geg_l_subnet_info` tests
3674+
def test_get_subnet_info_success(mocker, subtensor):
3675+
"""Test get_subnet_info returns correct data when subnet information is found."""
3676+
# Prep
3677+
netuid = mocker.Mock()
3678+
block = mocker.Mock()
3679+
3680+
mocker.patch.object(subtensor, "query_runtime_api")
3681+
mocker.patch.object(
3682+
subtensor_module.SubnetInfo,
3683+
"from_dict",
3684+
)
3685+
3686+
# Call
3687+
result = subtensor.get_subnet_info(netuid=netuid, block=block)
3688+
3689+
# Asserts
3690+
subtensor.query_runtime_api.assert_called_once_with(
3691+
runtime_api="SubnetInfoRuntimeApi",
3692+
method="get_subnet_info_v2",
3693+
params=[netuid],
3694+
block=block,
3695+
)
3696+
subtensor_module.SubnetInfo.from_dict.assert_called_once_with(
3697+
subtensor.query_runtime_api.return_value,
3698+
)
3699+
assert result == subtensor_module.SubnetInfo.from_dict.return_value
3700+
3701+
3702+
def test_get_subnet_info_no_data(mocker, subtensor):
3703+
"""Test get_subnet_info returns None."""
3704+
# Prep
3705+
netuid = mocker.Mock()
3706+
block = mocker.Mock()
3707+
mocker.patch.object(subtensor_module.SubnetInfo, "from_dict")
3708+
mocker.patch.object(subtensor, "query_runtime_api", return_value=None)
3709+
3710+
# Call
3711+
result = subtensor.get_subnet_info(netuid=netuid, block=block)
3712+
3713+
# Asserts
3714+
subtensor.query_runtime_api.assert_called_once_with(
3715+
runtime_api="SubnetInfoRuntimeApi",
3716+
method="get_subnet_info_v2",
3717+
params=[netuid],
3718+
block=block,
3719+
)
3720+
subtensor_module.SubnetInfo.from_dict.assert_not_called()
3721+
assert result is None
3722+
3723+
3724+
@pytest.mark.parametrize(
3725+
"call_return, expected",
3726+
[[10, 111], [None, None], [0, 121]],
3727+
)
3728+
def test_get_next_epoch_start_block(mocker, subtensor, call_return, expected):
3729+
"""Check that get_next_epoch_start_block returns the correct value."""
3730+
# Prep
3731+
netuid = mocker.Mock()
3732+
block = 20
3733+
3734+
mocked_blocks_since_last_step = mocker.Mock(return_value=call_return)
3735+
subtensor.blocks_since_last_step = mocked_blocks_since_last_step
3736+
3737+
mocker.patch.object(subtensor, "tempo", return_value=100)
3738+
3739+
# Call
3740+
result = subtensor.get_next_epoch_start_block(netuid=netuid, block=block)
3741+
3742+
# Asserts
3743+
mocked_blocks_since_last_step.assert_called_once_with(
3744+
netuid=netuid,
3745+
block=block,
3746+
)
3747+
subtensor.tempo.assert_called_once_with(netuid=netuid, block=block)
3748+
assert result == expected

0 commit comments

Comments
 (0)