diff --git a/CHANGELOG.md b/CHANGELOG.md index c49a34c9e9..1e4c16a27c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 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", diff --git a/tests/e2e_tests/test_root_claim.py b/tests/e2e_tests/test_root_claim.py index fa541d6d70..1fadb2ca9d 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): @@ -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 @@ -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 @@ -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}" ) @@ -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}" ) 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): 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 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