Skip to content

Commit cbdb352

Browse files
authored
Merge pull request #3032 from opentensor/release/9.10.0
Release/9.10.0
2 parents 1dc8d82 + 42d593d commit cbdb352

34 files changed

+1126
-377
lines changed

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

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,41 @@ jobs:
3232
steps:
3333
- name: Check-out repository under $GITHUB_WORKSPACE
3434
uses: actions/checkout@v4
35+
with:
36+
fetch-depth: 0
37+
ref: ${{ github.event.pull_request.head.sha }}
38+
39+
- name: Set up Python
40+
uses: actions/setup-python@v5
41+
with:
42+
python-version: '3.11'
43+
44+
- name: Install deps for collection
45+
run: |
46+
python -m pip install --upgrade pip
47+
pip install -e ".[dev]"
3548
3649
- name: Find test files
3750
id: get-tests
38-
run: |
39-
test_files=$(find tests/e2e_tests -name "test*.py" | jq -R -s -c 'split("\n") | map(select(. != ""))')
40-
# keep it here for future debug
41-
# test_files=$(find tests/e2e_tests -type f -name "test*.py" | grep -E 'test_(hotkeys|staking)\.py$' | jq -R -s -c 'split("\n") | map(select(. != ""))')
42-
echo "Found test files: $test_files"
43-
echo "test-files=$test_files" >> "$GITHUB_OUTPUT"
4451
shell: bash
52+
run: |
53+
set -euo pipefail
54+
test_matrix=$(
55+
pytest -q --collect-only tests/e2e_tests \
56+
| sed -n '/^e2e_tests\//p' \
57+
| sed 's|^|tests/|' \
58+
| jq -R -s -c '
59+
split("\n")
60+
| map(select(. != ""))
61+
| map({nodeid: ., label: (sub("^tests/e2e_tests/"; ""))})
62+
'
63+
)
64+
echo "Found tests: $test_matrix"
65+
echo "test-files=$test_matrix" >> "$GITHUB_OUTPUT"
4566
4667
# Pull docker image
4768
pull-docker-image:
69+
needs: find-tests
4870
runs-on: ubuntu-latest
4971
outputs:
5072
image-name: ${{ steps.set-image.outputs.image }}
@@ -111,23 +133,26 @@ jobs:
111133

112134
# Job to run tests in parallel
113135
run-fast-blocks-e2e-test:
114-
name: "FB: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}"
136+
name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}"
115137
needs:
116138
- find-tests
117139
- pull-docker-image
118140
runs-on: ubuntu-latest
119141
timeout-minutes: 45
120142
strategy:
121143
fail-fast: false # Allow other matrix jobs to run even if this job fails
122-
max-parallel: 32 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner)
144+
max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner)
123145
matrix:
124146
os:
125147
- ubuntu-latest
126-
test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }}
148+
test: ${{ fromJson(needs.find-tests.outputs.test-files) }}
127149
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
128150
steps:
129151
- name: Check-out repository
130152
uses: actions/checkout@v4
153+
with:
154+
fetch-depth: 0
155+
ref: ${{ github.event.pull_request.head.sha }}
131156

132157
- name: Set up Python ${{ matrix.python-version }}
133158
uses: actions/setup-python@v5
@@ -154,7 +179,7 @@ jobs:
154179
run: |
155180
for i in 1 2 3; do
156181
echo "::group::🔁 Test attempt $i"
157-
if uv run pytest ${{ matrix.test-file }} -s; then
182+
if uv run pytest "${{ matrix.test.nodeid }}" -s; then
158183
echo "✅ Tests passed on attempt $i"
159184
echo "::endgroup::"
160185
exit 0

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,20 @@
11
# Changelog
22

3+
## 9.10.0 /2025-08-28
4+
5+
## What's Changed
6+
* Fixes broken e2e tests by @thewhaleking in https://github.com/opentensor/bittensor/pull/3020
7+
* Add async crv4 e2e test by @basfroman in https://github.com/opentensor/bittensor/pull/3022
8+
* Use `TimelockedWeightCommits` instead of `CRV3WeightCommitsV2` by @basfroman in https://github.com/opentensor/bittensor/pull/3023
9+
* fix: reflect correct return types for get_delegated by @Arthurdw in https://github.com/opentensor/bittensor/pull/3016
10+
* Fix `flaky` e2e test (tests.e2e_tests.test_staking.test_safe_staking_scenarios) by @basfroman in https://github.com/opentensor/bittensor/pull/3025
11+
* Separation of test modules into separate text elements as independent matrix elements by @basfroman in https://github.com/opentensor/bittensor/pull/3027
12+
* Improve `move_stake` extrinsic (add `move_all_stake` parameter) by @basfroman in https://github.com/opentensor/bittensor/pull/3028
13+
* Fix tests related with disabled `sudo_set_commit_reveal_weights_enabled` by @basfroman in https://github.com/opentensor/bittensor/pull/3026
14+
15+
16+
**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.9.0...v9.10.0
17+
318
## 9.9.0 /2025-08-11
419

520
## What's Changed

bittensor/core/async_subtensor.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,7 +1880,7 @@ async def get_delegated(
18801880
block: Optional[int] = None,
18811881
block_hash: Optional[str] = None,
18821882
reuse_block: bool = False,
1883-
) -> list[tuple[DelegateInfo, Balance]]:
1883+
) -> list[DelegatedInfo]:
18841884
"""
18851885
Retrieves a list of delegates and their associated stakes for a given coldkey. This function identifies the
18861886
delegates that a specific account has staked tokens on.
@@ -1892,7 +1892,7 @@ async def get_delegated(
18921892
reuse_block: Whether to reuse the last-used blockchain block hash.
18931893
18941894
Returns:
1895-
A list of tuples, each containing a delegate's information and staked amount.
1895+
A list containing the delegated information for the specified coldkey.
18961896
18971897
This function is important for account holders to understand their stake allocations and their involvement in
18981898
the network's delegation and consensus mechanisms.
@@ -2519,6 +2519,7 @@ async def get_next_epoch_start_block(
25192519
netuid=netuid, block=block, block_hash=block_hash, reuse_block=reuse_block
25202520
)
25212521

2522+
block = block or await self.substrate.get_block_number(block_hash=block_hash)
25222523
if block and blocks_since_last_step is not None and tempo:
25232524
return block - blocks_since_last_step + tempo + 1
25242525
return None
@@ -2747,6 +2748,45 @@ async def get_subnet_prices(
27472748
prices.update({0: Balance.from_tao(1)})
27482749
return prices
27492750

2751+
async def get_timelocked_weight_commits(
2752+
self,
2753+
netuid: int,
2754+
block: Optional[int] = None,
2755+
block_hash: Optional[str] = None,
2756+
reuse_block: bool = False,
2757+
) -> list[tuple[str, int, str, int]]:
2758+
"""
2759+
Retrieves CRv4 weight commit information for a specific subnet.
2760+
2761+
Arguments:
2762+
netuid (int): The unique identifier of the subnet.
2763+
block (Optional[int]): The blockchain block number for the query. Default is ``None``.
2764+
block_hash: The hash of the block to retrieve the stake from. Do not specify if using block
2765+
or reuse_block
2766+
reuse_block: Whether to use the last-used block. Do not set if using block_hash or block.
2767+
2768+
Returns:
2769+
A list of commit details, where each item contains:
2770+
- ss58_address: The address of the committer.
2771+
- commit_block: The block number when the commitment was made.
2772+
- commit_message: The commit message.
2773+
- reveal_round: The round when the commitment was revealed.
2774+
2775+
The list may be empty if there are no commits found.
2776+
"""
2777+
block_hash = await self.determine_block_hash(
2778+
block=block, block_hash=block_hash, reuse_block=reuse_block
2779+
)
2780+
result = await self.substrate.query_map(
2781+
module="SubtensorModule",
2782+
storage_function="TimelockedWeightCommits",
2783+
params=[netuid],
2784+
block_hash=block_hash,
2785+
)
2786+
2787+
commits = result.records[0][1] if result.records else []
2788+
return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits]
2789+
27502790
# TODO: remove unused parameters in SDK.v10
27512791
async def get_unstake_fee(
27522792
self,
@@ -4617,10 +4657,11 @@ async def move_stake(
46174657
origin_netuid: int,
46184658
destination_hotkey: str,
46194659
destination_netuid: int,
4620-
amount: Balance,
4660+
amount: Optional[Balance] = None,
46214661
wait_for_inclusion: bool = True,
46224662
wait_for_finalization: bool = False,
46234663
period: Optional[int] = None,
4664+
move_all_stake: bool = False,
46244665
) -> bool:
46254666
"""
46264667
Moves stake to a different hotkey and/or subnet.
@@ -4637,6 +4678,7 @@ async def move_stake(
46374678
period: The number of blocks during which the transaction will remain valid after it's
46384679
submitted. If the transaction is not included in a block within that number of blocks, it will expire
46394680
and be rejected. You can think of it as an expiration date for the transaction.
4681+
move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey.
46404682
46414683
Returns:
46424684
success: True if the stake movement was successful.
@@ -4653,6 +4695,7 @@ async def move_stake(
46534695
wait_for_inclusion=wait_for_inclusion,
46544696
wait_for_finalization=wait_for_finalization,
46554697
period=period,
4698+
move_all_stake=move_all_stake,
46564699
)
46574700

46584701
async def register(
@@ -5548,7 +5591,7 @@ async def unstake(
55485591
self,
55495592
wallet: "Wallet",
55505593
hotkey_ss58: Optional[str] = None,
5551-
netuid: Optional[int] = None,
5594+
netuid: Optional[int] = None, # TODO why is this optional?
55525595
amount: Optional[Balance] = None,
55535596
wait_for_inclusion: bool = True,
55545597
wait_for_finalization: bool = False,

bittensor/core/extrinsics/asyncex/move_stake.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -305,28 +305,34 @@ async def move_stake_extrinsic(
305305
wait_for_inclusion: bool = True,
306306
wait_for_finalization: bool = False,
307307
period: Optional[int] = None,
308+
move_all_stake: bool = False,
308309
) -> bool:
309310
"""
310311
Moves stake from one hotkey to another within subnets in the Bittensor network.
311312
312313
Args:
313-
subtensor (Subtensor): The subtensor instance to interact with the blockchain.
314-
wallet (Wallet): The wallet containing the coldkey to authorize the move.
315-
origin_hotkey (str): SS58 address of the origin hotkey associated with the stake.
316-
origin_netuid (int): Network UID of the origin subnet.
317-
destination_hotkey (str): SS58 address of the destination hotkey.
318-
destination_netuid (int): Network UID of the destination subnet.
319-
amount (Balance): The amount of stake to move as a `Balance` object.
320-
wait_for_inclusion (bool): If True, waits for transaction inclusion in a block. Defaults to True.
321-
wait_for_finalization (bool): If True, waits for transaction finalization. Defaults to False.
322-
period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If
323-
the transaction is not included in a block within that number of blocks, it will expire and be rejected.
324-
You can think of it as an expiration date for the transaction.
314+
subtensor: The subtensor instance to interact with the blockchain.
315+
wallet: The wallet containing the coldkey to authorize the move.
316+
origin_hotkey: SS58 address of the origin hotkey associated with the stake.
317+
origin_netuid: Network UID of the origin subnet.
318+
destination_hotkey: SS58 address of the destination hotkey.
319+
destination_netuid: Network UID of the destination subnet.
320+
amount: The amount of stake to move as a `Balance` object.
321+
wait_for_inclusion: If True, waits for transaction inclusion in a block. Defaults to True.
322+
wait_for_finalization: If True, waits for transaction finalization. Defaults to False.
323+
period: The number of blocks during which the transaction will remain valid after it's submitted. If the
324+
transaction is not included in a block within that number of blocks, it will expire and be rejected. You can
325+
think of it as an expiration date for the transaction.
326+
move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey.
325327
326328
Returns:
327329
bool: True if the move was successful, False otherwise.
328330
"""
329-
amount.set_unit(netuid=origin_netuid)
331+
if not amount and not move_all_stake:
332+
logging.error(
333+
":cross_mark: [red]Failed[/red]: Please specify an `amount` or `move_all_stake` argument to move stake."
334+
)
335+
return False
330336

331337
# Check sufficient stake
332338
stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest(
@@ -338,13 +344,18 @@ async def move_stake_extrinsic(
338344
origin_netuid=origin_netuid,
339345
destination_netuid=destination_netuid,
340346
)
341-
if stake_in_origin < amount:
347+
if move_all_stake:
348+
amount = stake_in_origin
349+
350+
elif stake_in_origin < amount:
342351
logging.error(
343352
f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {origin_hotkey}. "
344353
f"Stake: {stake_in_origin}, amount: {amount}"
345354
)
346355
return False
347356

357+
amount.set_unit(netuid=origin_netuid)
358+
348359
try:
349360
logging.info(
350361
f"Moving stake from hotkey [blue]{origin_hotkey}[/blue] to hotkey [blue]{destination_hotkey}[/blue]\n"

bittensor/core/extrinsics/asyncex/registration.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import asyncio
1111
from typing import Optional, Union, TYPE_CHECKING
1212

13+
from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee
1314
from bittensor.utils import unlock_key
1415
from bittensor.utils.btlogging import logging
1516
from bittensor.utils.registration import log_no_torch_error, create_pow_async, torch
@@ -57,6 +58,12 @@ async def _do_burned_register(
5758
"hotkey": wallet.hotkey.ss58_address,
5859
},
5960
)
61+
fee = await get_extrinsic_fee(
62+
subtensor=subtensor, call=call, keypair=wallet.coldkeypub
63+
)
64+
logging.info(
65+
f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]."
66+
)
6067
return await subtensor.sign_and_send_extrinsic(
6168
call=call,
6269
wallet=wallet,
@@ -127,7 +134,6 @@ async def burned_register_extrinsic(
127134
return True
128135

129136
logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]")
130-
logging.info(f"Recycling {recycle_amount} to register on subnet:{netuid}")
131137

132138
success, err_msg = await _do_burned_register(
133139
subtensor=subtensor,

bittensor/core/extrinsics/asyncex/unstaking.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from typing import Optional, TYPE_CHECKING
33

44
from async_substrate_interface.errors import SubstrateRequestException
5-
5+
from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee
66
from bittensor.core.extrinsics.utils import get_old_stakes
77
from bittensor.utils import unlock_key, format_error_message
88
from bittensor.utils.balance import Balance
@@ -114,14 +114,14 @@ async def unstake_extrinsic(
114114
else:
115115
price_with_tolerance = base_price * (1 - rate_tolerance)
116116

117-
logging.info(
117+
logging_info = (
118118
f":satellite: [magenta]Safe Unstaking from:[/magenta] "
119119
f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green], "
120120
f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], "
121121
f"price limit: [green]{price_with_tolerance}[/green], "
122122
f"original price: [green]{base_price}[/green], "
123123
f"with partial unstake: [green]{allow_partial_stake}[/green] "
124-
f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]"
124+
f"on [blue]{subtensor.network}[/blue]"
125125
)
126126

127127
limit_price = Balance.from_tao(price_with_tolerance).rao
@@ -133,10 +133,10 @@ async def unstake_extrinsic(
133133
)
134134
call_function = "remove_stake_limit"
135135
else:
136-
logging.info(
136+
logging_info = (
137137
f":satellite: [magenta]Unstaking from:[/magenta] "
138138
f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green] "
139-
f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]"
139+
f"on [blue]{subtensor.network}[/blue]"
140140
)
141141
call_function = "remove_stake"
142142

@@ -145,6 +145,10 @@ async def unstake_extrinsic(
145145
call_function=call_function,
146146
call_params=call_params,
147147
)
148+
fee = await get_extrinsic_fee(
149+
subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid
150+
)
151+
logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]")
148152
success, message = await subtensor.sign_and_send_extrinsic(
149153
call=call,
150154
wallet=wallet,
@@ -381,10 +385,6 @@ async def unstake_multiple_extrinsic(
381385
continue
382386

383387
try:
384-
logging.info(
385-
f"Unstaking [blue]{unstaking_balance}[/blue] from hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: "
386-
f"[blue]{netuid}[/blue]"
387-
)
388388
call = await subtensor.substrate.compose_call(
389389
call_module="SubtensorModule",
390390
call_function="remove_stake",
@@ -394,6 +394,13 @@ async def unstake_multiple_extrinsic(
394394
"netuid": netuid,
395395
},
396396
)
397+
fee = await get_extrinsic_fee(
398+
subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid
399+
)
400+
logging.info(
401+
f"Unstaking [blue]{unstaking_balance}[/blue] from hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: "
402+
f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]"
403+
)
397404

398405
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
399406
call=call,

0 commit comments

Comments
 (0)