From 17a5933cd65775f50d6399538726f18561936369 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 1 Jun 2026 15:18:12 +0200 Subject: [PATCH 1/9] Fix for flaky root claims tests (copies the +4 blocks logic from test_root_claim_swap_async) --- tests/e2e_tests/test_root_claim.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_root_claim.py b/tests/e2e_tests/test_root_claim.py index fa541d6d70..7390cb730d 100644 --- a/tests/e2e_tests/test_root_claim.py +++ b/tests/e2e_tests/test_root_claim.py @@ -673,7 +673,9 @@ def test_root_claim_keep_with_random_auto_claims( next_epoch_start_block = subtensor.subnets.get_next_epoch_start_block( root_sn.netuid ) - subtensor.wait_for_block(next_epoch_start_block) + # +4 blocks: root dividend emission and the auto-claim land a few blocks + # into the new epoch, not at the exact boundary (see test_root_claim_swap_async). + subtensor.wait_for_block(next_epoch_start_block + 4) # Check Charlie stake and claimed claimed_stake_charlie = subtensor.staking.get_stake( @@ -806,7 +808,9 @@ async def test_root_claim_keep_with_random_auto_claims_async( 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 blocks: root dividend emission and the auto-claim land a few blocks + # into the new epoch, not at the exact boundary (see test_root_claim_swap_async). + 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( From e40a11f43092e3d60f028aed379ee25b416d81a2 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 1 Jun 2026 15:34:02 +0200 Subject: [PATCH 2/9] Fix for flaky test_staking tests --- tests/e2e_tests/test_staking.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index fc96720ef1..dac4b3a7bc 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -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( @@ -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): From 839bfc8efac610568e475f359171dafbc0812536 Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 1 Jun 2026 15:55:21 +0200 Subject: [PATCH 3/9] More robust solution --- tests/e2e_tests/test_root_claim.py | 123 ++++++++++++++++++----------- 1 file changed, 77 insertions(+), 46 deletions(-) diff --git a/tests/e2e_tests/test_root_claim.py b/tests/e2e_tests/test_root_claim.py index 7390cb730d..42f2e99f9e 100644 --- a/tests/e2e_tests/test_root_claim.py +++ b/tests/e2e_tests/test_root_claim.py @@ -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): @@ -209,19 +215,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 @@ -670,29 +685,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 - ) - # +4 blocks: root dividend emission and the auto-claim land a few blocks - # into the new epoch, not at the exact boundary (see test_root_claim_swap_async). - subtensor.wait_for_block(next_epoch_start_block + 4) + # 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}" ) @@ -805,29 +828,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) - ) - # +4 blocks: root dividend emission and the auto-claim land a few blocks - # into the new epoch, not at the exact boundary (see test_root_claim_swap_async). - await async_subtensor.wait_for_block(next_epoch_start_block + 4) + # 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}" ) From 8c6a37dfd9036e799a522fa2937fcd1058a929fb Mon Sep 17 00:00:00 2001 From: BD Himes Date: Mon, 1 Jun 2026 19:41:10 +0200 Subject: [PATCH 4/9] Apply same logic to test_root_claim_swap --- tests/e2e_tests/test_root_claim.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/e2e_tests/test_root_claim.py b/tests/e2e_tests/test_root_claim.py index 42f2e99f9e..1fadb2ca9d 100644 --- a/tests/e2e_tests/test_root_claim.py +++ b/tests/e2e_tests/test_root_claim.py @@ -111,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 From 23fe1fd9a5bfd2f4645c089dc0e26b0b7517ffe6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 2 Jun 2026 16:31:20 -0700 Subject: [PATCH 5/9] improve `mock_aio_response` fixture --- tests/unit_tests/conftest.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 7b13c7893e..9014d5a165 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -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 From 613f532cdd8c838d092424abda4ced688ea1f0ec Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 2 Jun 2026 16:31:30 -0700 Subject: [PATCH 6/9] remove unused import --- tests/unit_tests/test_dendrite.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit_tests/test_dendrite.py b/tests/unit_tests/test_dendrite.py index 38b2f8929f..e58a74af57 100644 --- a/tests/unit_tests/test_dendrite.py +++ b/tests/unit_tests/test_dendrite.py @@ -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 From e2b024d7d02ddd9fd1bd121ab146a474757f1eab Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 11 Jun 2026 21:01:19 +0200 Subject: [PATCH 7/9] Pins cyscale to 0.4.0, update changelog+version --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c49a34c9e9..a0ed8a126e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # 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` + +**Full Changelog**: https://github.com/latent-to/bittensor/compare/v10.4.0...v10.4.1 + ## 10.4.0 /2026-05-28 ## What's Changed diff --git a/pyproject.toml b/pyproject.toml index bd054e8ce2..e7f17a71d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = [ @@ -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", From b6a1b99524651f3ab45712674b30ec68f0474b7b Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 11 Jun 2026 21:04:23 +0200 Subject: [PATCH 8/9] Pins cyscale to 0.4.0, update changelog+version --- CHANGELOG.md | 8 ++++++++ pyproject.toml | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c49a34c9e9..a0ed8a126e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # 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` + +**Full Changelog**: https://github.com/latent-to/bittensor/compare/v10.4.0...v10.4.1 + ## 10.4.0 /2026-05-28 ## What's Changed diff --git a/pyproject.toml b/pyproject.toml index bd054e8ce2..e7f17a71d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 = [ @@ -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", From 6536631711d76a37db7b373018fb0be514121f5c Mon Sep 17 00:00:00 2001 From: BD Himes Date: Thu, 11 Jun 2026 21:25:23 +0200 Subject: [PATCH 9/9] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0ed8a126e..1e4c16a27c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ## 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