Skip to content
This repository was archived by the owner on Jul 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 10.4.1 /2026-06-11

## What's Changed

* Pins cyscale to 0.4.0 to avoid accidental upgrades which break `Subtensor.get_root_claimable_all_rates`
* Fix for newer aiohttp versions in testing
* Improves flaky tests

**Full Changelog**: https://github.com/latent-to/bittensor/compare/v10.4.0...v10.4.1

## 10.4.0 /2026-05-28

## What's Changed
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "bittensor"
version = "10.4.0"
version = "10.4.1"
description = "Bittensor SDK"
readme = "README.md"
authors = [
Expand All @@ -29,7 +29,7 @@ dependencies = [
"retry==0.9.2",
"requests>=2.33.0,<3.0",
"pydantic>=2.3,<3",
"cyscale>=0.3.3,<1.0.0",
"cyscale==0.4.0",
"uvicorn",
"bittensor-drand>=1.3.0,<2.0.0",
"bittensor-wallet>=4.1.0",
Expand Down
146 changes: 95 additions & 51 deletions tests/e2e_tests/test_root_claim.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
)

PROOF_COUNTER = 2
# Max epochs to wait for an emission/auto-claim to move a balance before failing.
# A strict increase isn't guaranteed in any single epoch window: root dividends to
# a small stake can round to ~0, the distribution can land a few blocks past the
# boundary, and random auto-claims may skip a coldkey in a given epoch. So we poll
# across a few epochs for the increase rather than demanding it at one boundary.
MAX_EPOCHS_FOR_INCREASE = 3


def test_root_claim_swap(subtensor, alice_wallet, bob_wallet, charlie_wallet):
Expand Down Expand Up @@ -105,16 +111,25 @@ def test_root_claim_swap(subtensor, alice_wallet, bob_wallet, charlie_wallet):

# Proof that ROOT stake is changing each last epoch block
while proof_counter > 0:
next_epoch_start_block = subtensor.subnets.get_next_epoch_start_block(
netuid=root_sn.netuid
)
subtensor.wait_for_block(block=next_epoch_start_block)
charlie_root_stake = subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=root_sn.netuid,
# Poll across up to MAX_EPOCHS_FOR_INCREASE epochs for the stake to grow.
charlie_root_stake = prev_root_stake
epochs_left = MAX_EPOCHS_FOR_INCREASE
while charlie_root_stake <= prev_root_stake and epochs_left > 0:
next_epoch_start_block = subtensor.subnets.get_next_epoch_start_block(
netuid=root_sn.netuid
)
subtensor.wait_for_block(block=next_epoch_start_block + 4)
charlie_root_stake = subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=root_sn.netuid,
)
epochs_left -= 1

assert charlie_root_stake > prev_root_stake, (
f"Stake did not increase over {MAX_EPOCHS_FOR_INCREASE} epochs: "
f"{charlie_root_stake} <= {prev_root_stake}"
)
assert charlie_root_stake > prev_root_stake
prev_root_stake = charlie_root_stake
proof_counter -= 1

Expand Down Expand Up @@ -209,19 +224,28 @@ async def test_root_claim_swap_async(

# Proof that ROOT stake is changing each last epoch block
while proof_counter > 0:
next_epoch_start_block = (
await async_subtensor.subnets.get_next_epoch_start_block(
netuid=root_sn.netuid
# Poll across up to MAX_EPOCHS_FOR_INCREASE epochs for the stake to grow.
charlie_root_stake = prev_root_stake
epochs_left = MAX_EPOCHS_FOR_INCREASE
while charlie_root_stake <= prev_root_stake and epochs_left > 0:
next_epoch_start_block = (
await async_subtensor.subnets.get_next_epoch_start_block(
netuid=root_sn.netuid
)
)
)
await async_subtensor.wait_for_block(block=next_epoch_start_block + 4)
await async_subtensor.wait_for_block(block=next_epoch_start_block + 4)

charlie_root_stake = await async_subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=root_sn.netuid,
charlie_root_stake = await async_subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=root_sn.netuid,
)
epochs_left -= 1

assert charlie_root_stake > prev_root_stake, (
f"Stake did not increase over {MAX_EPOCHS_FOR_INCREASE} epochs: "
f"{charlie_root_stake} <= {prev_root_stake}"
)
assert charlie_root_stake > prev_root_stake
prev_root_stake = charlie_root_stake
proof_counter -= 1

Expand Down Expand Up @@ -670,27 +694,37 @@ def test_root_claim_keep_with_random_auto_claims(

# Wait for epochs and check that stake increases due to random auto claims
while proof_counter > 0:
next_epoch_start_block = subtensor.subnets.get_next_epoch_start_block(
root_sn.netuid
)
subtensor.wait_for_block(next_epoch_start_block)
# A random auto-claim may skip Charlie in any single epoch, and the claim
# lands a few blocks past the boundary, so poll across up to
# MAX_EPOCHS_FOR_INCREASE epochs (+4 blocks each) for the claim to land.
claimed_stake_charlie = prev_claimed_stake_charlie
root_claimed_charlie = prev_root_claimed_charlie
epochs_left = MAX_EPOCHS_FOR_INCREASE
while claimed_stake_charlie <= prev_claimed_stake_charlie and epochs_left > 0:
next_epoch_start_block = subtensor.subnets.get_next_epoch_start_block(
root_sn.netuid
)
subtensor.wait_for_block(next_epoch_start_block + 4)

# Check Charlie stake and claimed
claimed_stake_charlie = subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
root_claimed_charlie = subtensor.staking.get_root_claimed(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
epochs_left -= 1

# Check Charlie stake and claimed
claimed_stake_charlie = subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
assert claimed_stake_charlie > prev_claimed_stake_charlie, (
f"Stake did not increase: {claimed_stake_charlie} <= {prev_claimed_stake_charlie}"
f"Stake did not increase over {MAX_EPOCHS_FOR_INCREASE} epochs: "
f"{claimed_stake_charlie} <= {prev_claimed_stake_charlie}"
)
prev_claimed_stake_charlie = claimed_stake_charlie

root_claimed_charlie = subtensor.staking.get_root_claimed(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
assert root_claimed_charlie > prev_root_claimed_charlie, (
f"Root claimed did not increase: {root_claimed_charlie} <= {prev_root_claimed_charlie}"
)
Expand Down Expand Up @@ -803,27 +837,37 @@ async def test_root_claim_keep_with_random_auto_claims_async(

# Wait for epochs and check that stake increases due to random auto claims
while proof_counter > 0:
next_epoch_start_block = (
await async_subtensor.subnets.get_next_epoch_start_block(root_sn.netuid)
)
await async_subtensor.wait_for_block(next_epoch_start_block)
# A random auto-claim may skip Charlie in any single epoch, and the claim
# lands a few blocks past the boundary, so poll across up to
# MAX_EPOCHS_FOR_INCREASE epochs (+4 blocks each) for the claim to land.
claimed_stake_charlie = prev_claimed_stake_charlie
root_claimed_charlie = prev_root_claimed_charlie
epochs_left = MAX_EPOCHS_FOR_INCREASE
while claimed_stake_charlie <= prev_claimed_stake_charlie and epochs_left > 0:
next_epoch_start_block = (
await async_subtensor.subnets.get_next_epoch_start_block(root_sn.netuid)
)
await async_subtensor.wait_for_block(next_epoch_start_block + 4)

# Check Charlie stake and claimed
claimed_stake_charlie = await async_subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
root_claimed_charlie = await async_subtensor.staking.get_root_claimed(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
epochs_left -= 1

# Check Charlie stake and claimed
claimed_stake_charlie = await async_subtensor.staking.get_stake(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
assert claimed_stake_charlie > prev_claimed_stake_charlie, (
f"Stake did not increase: {claimed_stake_charlie} <= {prev_claimed_stake_charlie}"
f"Stake did not increase over {MAX_EPOCHS_FOR_INCREASE} epochs: "
f"{claimed_stake_charlie} <= {prev_claimed_stake_charlie}"
)
prev_claimed_stake_charlie = claimed_stake_charlie

root_claimed_charlie = await async_subtensor.staking.get_root_claimed(
coldkey_ss58=charlie_wallet.coldkey.ss58_address,
hotkey_ss58=alice_wallet.hotkey.ss58_address,
netuid=sn2.netuid,
)
assert root_claimed_charlie > prev_root_claimed_charlie, (
f"Root claimed did not increase: {root_claimed_charlie} <= {prev_root_claimed_charlie}"
)
Expand Down
20 changes: 16 additions & 4 deletions tests/e2e_tests/test_staking.py
Original file line number Diff line number Diff line change
Expand Up @@ -1740,11 +1740,17 @@ def test_unstaking_with_limit(
# time for chain update
subtensor.wait_for_block(subtensor.block + 5)

# Make sure both unstake were successful.
# Make sure both unstakes were successful. unstake_all can leave a few rao
# of rounding dust because the removable alpha depends on dividend emission
# accrued up to that block, so assert each residual position is ~0 rather
# than requiring an empty list.
bob_stakes = subtensor.staking.get_stake_info_for_coldkey(
bob_wallet.coldkey.ss58_address
)
assert len(bob_stakes) == 0
for si in bob_stakes:
assert si.stake.rao == CloseInValue(0, 1000), (
f"Residual stake too large after unstake_all: {si}"
)


@pytest.mark.parametrize(
Expand Down Expand Up @@ -1841,11 +1847,17 @@ async def test_unstaking_with_limit_async(
# time for chain update
await async_subtensor.wait_for_block(await async_subtensor.block + 5)

# Make sure both unstake were successful.
# Make sure both unstakes were successful. unstake_all can leave a few rao
# of rounding dust because the removable alpha depends on dividend emission
# accrued up to that block, so assert each residual position is ~0 rather
# than requiring an empty list.
bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey(
bob_wallet.coldkey.ss58_address
)
assert len(bob_stakes) == 0
for si in bob_stakes:
assert si.stake.rao == CloseInValue(0, 1000), (
f"Residual stake too large after unstake_all: {si}"
)


def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet):
Expand Down
14 changes: 13 additions & 1 deletion tests/unit_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,19 @@ def force_legacy_torch_compatible_api(monkeypatch):


@pytest.fixture
def mock_aio_response():
def mock_aio_response(monkeypatch):
import aiohttp
from unittest.mock import Mock
from aioresponses import aioresponses

original_init = aiohttp.ClientResponse.__init__

def patched_init(self, *args, **kwargs):
if "stream_writer" not in kwargs:
kwargs["stream_writer"] = Mock()
original_init(self, *args, **kwargs)

monkeypatch.setattr(aiohttp.ClientResponse, "__init__", patched_init)
with aioresponses() as m:
yield m

Expand Down
1 change: 0 additions & 1 deletion tests/unit_tests/test_dendrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from unittest.mock import MagicMock, Mock

import aiohttp
from bittensor_wallet.mock import get_mock_wallet
import pytest

from bittensor.core.axon import Axon
Expand Down
Loading