From 5322803ad0133223f7f5ebdc2a5954bcf95d92b5 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 19:45:48 +0200 Subject: [PATCH 01/86] Revert "Merge pull request #2975 from opentensor/fix/roman/fix-after-devnet-ready-update-fee" This reverts commit 9665972a3ee80d511a516b37a50356d9ade95faf, reversing changes made to 81e0a44ac54334c6788df156458155c9861bc60a. --- tests/e2e_tests/test_delegate.py | 2 +- tests/e2e_tests/test_stake_fee.py | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 9fb4b9dd1a..4bd181ae2e 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -342,7 +342,7 @@ def test_nominator_min_required_stake( wallet=dave_wallet, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, - amount=Balance.from_tao(1000), + amount=Balance.from_tao(10_000), wait_for_inclusion=True, wait_for_finalization=True, ) diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 049ee3cbe7..b20440c651 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -19,7 +19,7 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): netuid = 2 root_netuid = 0 stake_amount = Balance.from_tao(100) # 100 TAO - min_stake_fee = Balance.from_tao(0.050354772) + min_stake_fee = Balance.from_tao(0.299076829) # Register subnet as Alice assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" @@ -32,9 +32,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): coldkey_ss58=alice_wallet.coldkeypub.ss58_address, hotkey_ss58=alice_wallet.hotkey.ss58_address, ) - assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object." + assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object" assert stake_fee_0 == min_stake_fee, ( - "Stake fee should be equal the minimum stake fee." + "Stake fee should be equal the minimum stake fee" ) # Test unstake fee @@ -44,11 +44,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): coldkey_ss58=alice_wallet.coldkeypub.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, ) - assert isinstance(unstake_fee_root, Balance), ( - "Stake fee should be a Balance object." - ) - assert unstake_fee_root == min_stake_fee, ( - "Root unstake fee should be equal the minimum stake fee." + assert isinstance(unstake_fee_root, Balance), "Stake fee should be a Balance object" + assert unstake_fee_root == Balance.from_rao(299076829), ( + "Root unstake fee should be 0." ) # Test various stake movement scenarios From 49547ed0300c0b6862feb576a48da9dcb80e5743 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 15:06:29 -0700 Subject: [PATCH 02/86] add `test_axon_async` --- tests/e2e_tests/test_axon.py | 97 +++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index 613027c691..61dff37431 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -2,13 +2,14 @@ import pytest +from bittensor import logging from bittensor.utils import networking @pytest.mark.asyncio async def test_axon(subtensor, templates, alice_wallet): """ - Test the Axon mechanism and successful registration on the network. + Test the Axon mechanism and successful registration on the network with sync Subtensor. Steps: 1. Register a subnet and register Alice @@ -19,17 +20,17 @@ async def test_axon(subtensor, templates, alice_wallet): AssertionError: If any of the checks or verifications fail """ - print("Testing test_axon") + logging.console.info("Testing test_axon") netuid = 2 # Register a subnet, netuid 2 - assert subtensor.register_subnet(alice_wallet), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" # Verify subnet created successfully - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" - metagraph = subtensor.metagraph(netuid) + metagraph = subtensor.metagraphs.metagraph(netuid) # Validate current metagraph stats old_axon = metagraph.axons[0] @@ -49,7 +50,7 @@ async def test_axon(subtensor, templates, alice_wallet): await asyncio.sleep(5) # Refresh the metagraph - metagraph = subtensor.metagraph(netuid) + metagraph = subtensor.metagraphs.metagraph(netuid) updated_axon = metagraph.axons[0] external_ip = networking.get_external_ip() @@ -80,4 +81,86 @@ async def test_axon(subtensor, templates, alice_wallet): "Coldkey mismatch after mining" ) - print("✅ Passed test_axon") + logging.console.success("✅ Passed test_axon") + + +@pytest.mark.asyncio +async def test_axon_async(async_subtensor, templates, alice_wallet): + """ + Test the Axon mechanism and successful registration on the network with async Subtensor. + + Steps: + 1. Register a subnet and register Alice + 2. Check if metagraph.axon is updated and check axon attributes + 3. Run Alice as a miner on subnet + 4. Check the metagraph again after running the miner and verify all attributes + Raises: + AssertionError: If any of the checks or verifications fail + """ + + logging.console.info("Testing test_axon") + + netuid = 2 + + # Register a subnet, netuid 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Subnet wasn't created" + ) + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + metagraph = await async_subtensor.metagraphs.metagraph(netuid) + + # Validate current metagraph stats + old_axon = metagraph.axons[0] + assert len(metagraph.axons) == 1, f"Expected 1 axon, but got {len(metagraph.axons)}" + assert old_axon.hotkey == alice_wallet.hotkey.ss58_address, ( + "Hotkey mismatch for the axon" + ) + assert old_axon.coldkey == alice_wallet.coldkey.ss58_address, ( + "Coldkey mismatch for the axon" + ) + assert old_axon.ip == "0.0.0.0", f"Expected IP 0.0.0.0, but got {old_axon.ip}" + assert old_axon.port == 0, f"Expected port 0, but got {old_axon.port}" + assert old_axon.ip_type == 0, f"Expected IP type 0, but got {old_axon.ip_type}" + + async with templates.miner(alice_wallet, netuid): + # Waiting for 5 seconds for metagraph to be updated + await asyncio.sleep(5) + + # Refresh the metagraph + metagraph = await async_subtensor.metagraphs.metagraph(netuid) + updated_axon = metagraph.axons[0] + external_ip = networking.get_external_ip() + + # Assert updated attributes + assert len(metagraph.axons) == 1, ( + f"Expected 1 axon, but got {len(metagraph.axons)} after mining" + ) + + assert len(metagraph.neurons) == 1, ( + f"Expected 1 neuron, but got {len(metagraph.neurons)}" + ) + + assert updated_axon.ip == external_ip, ( + f"Expected IP {external_ip}, but got {updated_axon.ip}" + ) + + assert updated_axon.ip_type == networking.ip_version(external_ip), ( + f"Expected IP type {networking.ip_version(external_ip)}, but got {updated_axon.ip_type}" + ) + + assert updated_axon.port == 8091, f"Expected port 8091, but got {updated_axon.port}" + + assert updated_axon.hotkey == alice_wallet.hotkey.ss58_address, ( + "Hotkey mismatch after mining" + ) + + assert updated_axon.coldkey == alice_wallet.coldkey.ss58_address, ( + "Coldkey mismatch after mining" + ) + + logging.console.success("✅ Passed test_axon_async") From 2ad1613eaa253fac599aa522c06e475a506cf769 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 15:15:58 -0700 Subject: [PATCH 03/86] improve `tests/e2e_tests/test_commit_reveal.py` --- tests/e2e_tests/test_commit_reveal.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 2531326583..197a45e02f 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -48,7 +48,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle ) # Verify subnet 2 created successfully - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( f"SN #{alice_subnet_netuid} wasn't created successfully" ) @@ -92,7 +92,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle ).weights_rate_limit == 0 ), "Failed to set weights_rate_limit" - assert subtensor.weights_rate_limit(netuid=alice_subnet_netuid) == 0 + assert subtensor.subnets.weights_rate_limit(netuid=alice_subnet_netuid) == 0 logging.console.success("sudo_set_weights_set_rate_limit executed: set to 0") # Change the tempo of the subnet @@ -183,7 +183,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle assert expected_commit_block in [commit_block - 1, commit_block, commit_block + 1] # Ensure no weights are available as of now - assert subtensor.weights(netuid=alice_subnet_netuid) == [] + assert subtensor.subnets.weights(netuid=alice_subnet_netuid) == [] logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset @@ -232,7 +232,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle @pytest.mark.asyncio -async def test_async_commit_and_reveal_weights_cr4( +async def test_commit_and_reveal_weights_cr4_async( local_chain, async_subtensor, alice_wallet ): """ @@ -267,7 +267,7 @@ async def test_async_commit_and_reveal_weights_cr4( ) # Verify subnet 2 created successfully - assert await async_subtensor.subnet_exists(alice_subnet_netuid), ( + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( f"SN #{alice_subnet_netuid} wasn't created successfully" ) @@ -310,7 +310,10 @@ async def test_async_commit_and_reveal_weights_cr4( netuid=alice_subnet_netuid ) ).weights_rate_limit == 0, "Failed to set weights_rate_limit" - assert await async_subtensor.weights_rate_limit(netuid=alice_subnet_netuid) == 0 + assert ( + await async_subtensor.subnets.weights_rate_limit(netuid=alice_subnet_netuid) + == 0 + ) logging.console.success("sudo_set_weights_set_rate_limit executed: set to 0") # Change the tempo of the subnet @@ -407,7 +410,7 @@ async def test_async_commit_and_reveal_weights_cr4( # assert expected_commit_block in [commit_block - 1, commit_block, commit_block + 1] # Ensure no weights are available as of now - assert await async_subtensor.weights(netuid=alice_subnet_netuid) == [] + assert await async_subtensor.subnets.weights(netuid=alice_subnet_netuid) == [] logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset From 546ccf485f848acc952923f4e8615d822c762201 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 16:08:56 -0700 Subject: [PATCH 04/86] add async version of `tests/e2e_tests/utils/chain_interactions.py` functions --- tests/e2e_tests/utils/chain_interactions.py | 132 ++++++++++++++++---- 1 file changed, 110 insertions(+), 22 deletions(-) diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 71692d316b..18422f1565 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -16,7 +16,12 @@ from bittensor import Wallet from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.subtensor import Subtensor - from async_substrate_interface import SubstrateInterface, ExtrinsicReceipt + from async_substrate_interface import ( + SubstrateInterface, + ExtrinsicReceipt, + AsyncSubstrateInterface, + AsyncExtrinsicReceipt, + ) def get_dynamic_balance(rao: int, netuid: int = 0): @@ -31,9 +36,7 @@ def sudo_set_hyperparameter_bool( value: bool, netuid: int, ) -> bool: - """ - Sets boolean hyperparameter value through AdminUtils. Mimics setting hyperparams - """ + """Sets boolean hyperparameter value through AdminUtils. Mimics setting hyperparams.""" call = substrate.compose_call( call_module="AdminUtils", call_function=call_function, @@ -48,6 +51,30 @@ def sudo_set_hyperparameter_bool( return response.is_success +async def async_sudo_set_hyperparameter_bool( + substrate: "AsyncSubstrateInterface", + wallet: "Wallet", + call_function: str, + value: bool, + netuid: int, +) -> bool: + """Sets boolean hyperparameter value through AdminUtils. Mimics setting hyperparams.""" + call = await substrate.compose_call( + call_module="AdminUtils", + call_function=call_function, + call_params={"netuid": netuid, "enabled": value}, + ) + extrinsic = await substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) + response = await substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + return await response.is_success + + def sudo_set_hyperparameter_values( substrate: "SubstrateInterface", wallet: "Wallet", @@ -55,9 +82,7 @@ def sudo_set_hyperparameter_values( call_params: dict, return_error_message: bool = False, ) -> Union[bool, tuple[bool, Optional[str]]]: - """ - Sets liquid alpha values using AdminUtils. Mimics setting hyperparams - """ + """Sets liquid alpha values using AdminUtils. Mimics setting hyperparams.""" call = substrate.compose_call( call_module="AdminUtils", call_function=call_function, @@ -95,13 +120,38 @@ async def wait_epoch(subtensor: "Subtensor", netuid: int = 1, **kwargs): await wait_interval(tempo, subtensor, netuid, **kwargs) +async def async_wait_epoch( + async_subtensor: "AsyncSubtensor", netuid: int = 1, **kwargs +): + """ + Waits for the next epoch to start on a specific subnet. + + Queries the tempo value from the Subtensor module and calculates the + interval based on the tempo. Then waits for the next epoch to start + by monitoring the current block number. + + Raises: + Exception: If the tempo cannot be determined from the chain. + """ + q_tempo = [ + v + async for (k, v) in await async_subtensor.query_map_subtensor("Tempo") + if k == netuid + ] + if len(q_tempo) == 0: + raise Exception("could not determine tempo") + tempo = q_tempo[0].value + logging.info(f"tempo = {tempo}") + await async_wait_interval(tempo, async_subtensor, netuid, **kwargs) + + def next_tempo(current_block: int, tempo: int) -> int: """ Calculates the next tempo block for a specific subnet. Args: - current_block (int): The current block number. - tempo (int): The tempo value for the subnet. + current_block: The current block number. + tempo: The tempo value for the subnet. Returns: int: The next tempo block number. @@ -188,9 +238,7 @@ async def async_wait_interval( def execute_and_wait_for_next_nonce( subtensor, wallet, sleep=0.25, timeout=60.0, max_retries=3 ): - """ - Decorator that ensures the nonce has been consumed after a blockchain extrinsic call. - """ + """Decorator that ensures the nonce has been consumed after a blockchain extrinsic call.""" def decorator(func): @functools.wraps(func) @@ -242,14 +290,14 @@ def sudo_set_admin_utils( Wraps the call in sudo to set hyperparameter values using AdminUtils. Args: - substrate (SubstrateInterface): Substrate connection. - wallet (Wallet): Wallet object with the keypair for signing. - call_function (str): The AdminUtils function to call. - call_params (dict): Parameters for the AdminUtils function. - call_module (str): The AdminUtils module to call. Defaults to "AdminUtils". + substrate: Substrate connection. + wallet: Wallet object with the keypair for signing. + call_function: The AdminUtils function to call. + call_params: Parameters for the AdminUtils function. + call_module: The AdminUtils module to call. Defaults to "AdminUtils". Returns: - tuple[bool, Optional[dict]]: (success status, error details). + tuple: (success status, error details). """ inner_call = substrate.compose_call( call_module=call_module, @@ -274,16 +322,56 @@ def sudo_set_admin_utils( return response.is_success, response.error_message -async def root_set_subtensor_hyperparameter_values( - substrate: "SubstrateInterface", +async def async_sudo_set_admin_utils( + substrate: "AsyncSubstrateInterface", wallet: "Wallet", call_function: str, call_params: dict, - return_error_message: bool = False, + call_module: str = "AdminUtils", ) -> tuple[bool, Optional[dict]]: """ - Sets liquid alpha values using AdminUtils. Mimics setting hyperparams + Wraps the call in sudo to set hyperparameter values using AdminUtils. + + Parameters: + substrate: Substrate connection. + wallet: Wallet object with the keypair for signing. + call_function: The AdminUtils function to call. + call_params: Parameters for the AdminUtils function. + call_module: The AdminUtils module to call. Defaults to "AdminUtils". + + Returns: + tuple: (success status, error details). """ + inner_call = await substrate.compose_call( + call_module=call_module, + call_function=call_function, + call_params=call_params, + ) + + sudo_call = await substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={"call": inner_call}, + ) + extrinsic = await substrate.create_signed_extrinsic( + call=sudo_call, keypair=wallet.coldkey + ) + response: "AsyncExtrinsicReceipt" = await substrate.submit_extrinsic( + extrinsic, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + return await response.is_success, await response.error_message + + +def root_set_subtensor_hyperparameter_values( + substrate: "SubstrateInterface", + wallet: "Wallet", + call_function: str, + call_params: dict, +) -> tuple[bool, Optional[dict]]: + """Sets liquid alpha values using AdminUtils. Mimics setting hyperparams.""" call = substrate.compose_call( call_module="SubtensorModule", call_function=call_function, From f19e6bafd569e61c9b71d00e93d0036083f7e8d9 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 00:33:30 -0700 Subject: [PATCH 05/86] add `raise_error=True` to `_do_commit_weights`. `subtensor.commit_weights` never increase `retries` --- bittensor/core/extrinsics/asyncex/weights.py | 1 + bittensor/core/extrinsics/commit_weights.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index cf993b37eb..bff8a5096d 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -64,6 +64,7 @@ async def _do_commit_weights( period=period, nonce_key="hotkey", sign_with="hotkey", + raise_error=True, ) diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index 511d1364ef..3a590d74b6 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -58,6 +58,7 @@ def _do_commit_weights( period=period, sign_with="hotkey", nonce_key="hotkey", + raise_error=True, ) From c856df78b9151185bf7f28b65c85584abef99205 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 00:43:50 -0700 Subject: [PATCH 06/86] add async tests in `tests/e2e_tests/test_commit_weights.py` --- tests/e2e_tests/test_commit_weights.py | 402 +++++++++++++++++++++++-- 1 file changed, 374 insertions(+), 28 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index e13964c34c..8e524f890a 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -1,10 +1,14 @@ import numpy as np import pytest import retry +import time from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_admin_utils, + async_sudo_set_hyperparameter_bool, + async_wait_epoch, sudo_set_admin_utils, sudo_set_hyperparameter_bool, execute_and_wait_for_next_nonce, @@ -26,12 +30,15 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa Raises: AssertionError: If any of the checks or verifications fail """ - netuid = subtensor.get_total_subnets() # 2 - set_tempo = 100 if subtensor.is_fast_blocks() else 10 - print("Testing test_commit_and_reveal_weights") + logging.console.info("Testing test_commit_and_reveal_weights") + + netuid = subtensor.subnets.get_total_subnets() # 2 + set_tempo = 100 if subtensor.chain.is_fast_blocks() else 10 # Register root as Alice - assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" + assert subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) # Verify subnet 2 created successfully assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" @@ -45,13 +52,16 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa netuid, ), "Unable to enable commit reveal on the subnet" - assert subtensor.commit_reveal_enabled(netuid), "Failed to enable commit/reveal" + assert subtensor.subnets.commit_reveal_enabled(netuid), ( + "Failed to enable commit/reveal" + ) assert ( - subtensor.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period == 1 + subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period + == 1 ), "Failed to set commit/reveal periods" - assert subtensor.weights_rate_limit(netuid=netuid) > 0, ( + assert subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( "Weights rate limit is below 0" ) @@ -67,9 +77,10 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa assert status is True assert ( - subtensor.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0 + subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit + == 0 ), "Failed to set weights_rate_limit" - assert subtensor.weights_rate_limit(netuid=netuid) == 0 + assert subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 # Increase subnet tempo so we have enough time to commit and reveal weights sudo_set_admin_utils( @@ -91,7 +102,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa ) # Commit weights - success, message = subtensor.commit_weights( + success, message = subtensor.extrinsics.commit_weights( alice_wallet, netuid, salt=salt, @@ -114,7 +125,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa assert commit_block > 0, f"Invalid block number: {commit_block}" # Query the WeightCommitRevealInterval storage map - assert subtensor.get_subnet_reveal_period_epochs(netuid) > 0, ( + assert subtensor.subnets.get_subnet_reveal_period_epochs(netuid) > 0, ( "Invalid RevealPeriodEpochs" ) @@ -122,7 +133,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa await wait_epoch(subtensor, netuid) # Reveal weights - success, message = subtensor.reveal_weights( + success, message = subtensor.extrinsics.reveal_weights( alice_wallet, netuid, uids=weight_uids, @@ -147,7 +158,167 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa assert weight_vals[0] == revealed_weights[0][1], ( f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights[0][1]}" ) - print("✅ Passed test_commit_and_reveal_weights") + logging.console.success("✅ Passed test_commit_and_reveal_weights") + + +@pytest.mark.asyncio +async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wallet): + """ + Tests the commit/reveal weights mechanism with subprocess disabled (CR1.0) with AsyncSubtensor. + + Steps: + 1. Register a subnet through Alice + 2. Enable the commit-reveal mechanism on subnet + 3. Lower the commit_reveal interval and rate limit + 4. Commit weights and verify + 5. Wait interval & reveal weights and verify + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing test_commit_and_reveal_weights_async") + + netuid = await async_subtensor.subnets.get_total_subnets() # 2 + set_tempo = 100 if await async_subtensor.chain.is_fast_blocks() else 10 + + # Register root as Alice + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) + + # Verify subnet 2 created successfully + assert await async_subtensor.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + # Enable commit_reveal on the subnet + assert await async_sudo_set_hyperparameter_bool( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=True, + netuid=netuid, + ), "Unable to enable commit reveal on the subnet" + + assert await async_subtensor.subnets.commit_reveal_enabled(netuid), ( + "Failed to enable commit/reveal" + ) + + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) + ).commit_reveal_period == 1, "Failed to set commit/reveal periods" + + assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( + "Weights rate limit is below 0" + ) + + # Lower the rate limit + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, + ) + + assert error is None + assert status is True + + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) + ).weights_rate_limit == 0, "Failed to set weights_rate_limit" + assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 + + # Increase subnet tempo so we have enough time to commit and reveal weights + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": netuid, + "tempo": set_tempo, + }, + ) + + # Commit-reveal values + uids = np.array([0], dtype=np.int64) + weights = np.array([0.1], dtype=np.float32) + salt = [18, 179, 107, 0, 165, 211, 141, 197] + weight_uids, weight_vals = convert_weights_and_uids_for_emit( + uids=uids, weights=weights + ) + + # Commit weights + success, message = await async_subtensor.extrinsics.commit_weights( + wallet=alice_wallet, + netuid=netuid, + salt=salt, + uids=weight_uids, + weights=weight_vals, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert success is True + + weight_commits = await async_subtensor.query_module( + module="SubtensorModule", + name="WeightCommits", + params=[netuid, alice_wallet.hotkey.ss58_address], + ) + # Assert that the committed weights are set correctly + assert weight_commits is not None, "Weight commit not found in storage" + commit_hash, commit_block, reveal_block, expire_block = weight_commits[0] + assert commit_block > 0, f"Invalid block number: {commit_block}" + + # Query the WeightCommitRevealInterval storage map + assert await async_subtensor.subnets.get_subnet_reveal_period_epochs(netuid) > 0, ( + "Invalid RevealPeriodEpochs" + ) + + # Wait until the reveal block range + await async_wait_epoch(async_subtensor, netuid) + + # Reveal weights + success, message = await async_subtensor.extrinsics.reveal_weights( + alice_wallet, + netuid, + uids=weight_uids, + weights=weight_vals, + salt=salt, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert success is True + + # Query the Weights storage map + revealed_weights = await async_subtensor.query_module( + module="SubtensorModule", + name="Weights", + params=[netuid, 0], # netuid and uid + ) + + # Assert that the revealed weights are set correctly + assert revealed_weights is not None, "Weight reveal not found in storage" + + assert weight_vals[0] == revealed_weights[0][1], ( + f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights[0][1]}" + ) + logging.console.success("✅ Passed test_commit_and_reveal_weights_async") + + +# Create different committed data to avoid coming into the pool's blacklist with the error +# Failed to commit weights: Subtensor returned `Custom type(1012)` error. This means: `Transaction is temporarily +# banned`.Failed to commit weights: Subtensor returned `Custom type(1012)` error. This means: `Transaction is +# temporarily banned`.` +def get_weights_and_salt(counter: int): + # Commit-reveal values + salt_ = [18, 179, 107, counter, 165, 211, 141, 197] + uids_ = np.array([0], dtype=np.int64) + weights_ = np.array([counter / 10], dtype=np.float32) + weight_uids_, weight_vals_ = convert_weights_and_uids_for_emit( + uids=uids_, weights=weights_ + ) + return salt_, weight_uids_, weight_vals_ @pytest.mark.asyncio @@ -165,13 +336,14 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing test_commit_and_reveal_weights") + subnet_tempo = 50 if subtensor.is_fast_blocks() else 10 netuid = subtensor.get_total_subnets() # 2 # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos subtensor.wait_for_block(subtensor.block + (subnet_tempo * 2) + 1) - print("Testing test_commit_and_reveal_weights") # Register root as Alice assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" @@ -226,20 +398,6 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # wait while weights_rate_limit changes applied. subtensor.wait_for_block(subnet_tempo + 1) - # Create different committed data to avoid coming into the pool's blacklist with the error - # Failed to commit weights: Subtensor returned `Custom type(1012)` error. This means: `Transaction is temporarily - # banned`.Failed to commit weights: Subtensor returned `Custom type(1012)` error. This means: `Transaction is - # temporarily banned`.` - def get_weights_and_salt(counter: int): - # Commit-reveal values - salt_ = [18, 179, 107, counter, 165, 211, 141, 197] - uids_ = np.array([0], dtype=np.int64) - weights_ = np.array([counter / 10], dtype=np.float32) - weight_uids_, weight_vals_ = convert_weights_and_uids_for_emit( - uids=uids_, weights=weights_ - ) - return salt_, weight_uids_, weight_vals_ - logging.console.info( f"[orange]Nonce before first commit_weights: " f"{subtensor.substrate.get_account_next_index(alice_wallet.hotkey.ss58_address)}[/orange]" @@ -299,3 +457,191 @@ def send_commit(salt_, weight_uids_, weight_vals_): assert len(weight_commits.value) == AMOUNT_OF_COMMIT_WEIGHTS, ( "Expected exact list of weight commits" ) + logging.console.success("Passed `test_commit_and_reveal_weights` test.") + + +@pytest.mark.asyncio +async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_wallet): + """ + Tests that committing weights doesn't re-use nonce in the transaction pool with AsyncSubtensor. + + Steps: + 1. Register a subnet through Alice + 2. Register Alice's neuron and add stake + 3. Enable the commit-reveal mechanism on subnet + 4. Lower the commit_reveal interval and rate limit + 5. Commit weights three times + 6. Assert that all commits succeeded + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing test_commit_and_reveal_weights") + + subnet_tempo = 50 if await async_subtensor.is_fast_blocks() else 10 + netuid = await async_subtensor.get_total_subnets() # 2 + + # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos + await async_subtensor.wait_for_block( + await async_subtensor.block + (subnet_tempo * 2) + 1 + ) + + # Register root as Alice + assert await async_subtensor.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) + + # Verify subnet 1 created successfully + assert await async_subtensor.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + # weights sensitive to epoch changes + assert await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": netuid, + "tempo": subnet_tempo, + }, + ) + + # Enable commit_reveal on the subnet + assert await async_sudo_set_hyperparameter_bool( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=True, + netuid=netuid, + ), "Unable to enable commit reveal on the subnet" + + assert await async_subtensor.commit_reveal_enabled(netuid), ( + "Failed to enable commit/reveal" + ) + + assert ( + await async_subtensor.get_subnet_hyperparameters(netuid=netuid) + ).commit_reveal_period == 1, "Failed to set commit/reveal periods" + + assert await async_subtensor.weights_rate_limit(netuid=netuid) > 0, ( + "Weights rate limit is below 0" + ) + + # Lower the rate limit + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, + ) + + assert error is None and status is True, f"Failed to set rate limit: {error}" + + assert ( + await async_subtensor.get_subnet_hyperparameters(netuid=netuid) + ).weights_rate_limit == 0, "Failed to set weights_rate_limit" + assert await async_subtensor.weights_rate_limit(netuid=netuid) == 0 + + # wait while weights_rate_limit changes applied. + await async_subtensor.wait_for_block(subnet_tempo + 1) + + logging.console.info( + f"[orange]Nonce before first commit_weights: " + f"{await async_subtensor.substrate.get_account_nonce(alice_wallet.hotkey.ss58_address)}[/orange]" + ) + + # 3 time doing call if nonce wasn't updated, then raise the error + + async def send_commit(salt_, weight_uids_, weight_vals_): + """ + To avoid adding asynchronous retrieval to dependencies, we implement a retrieval behavior with asynchronous + behavior. + """ + + async def send_commit_(): + success_, message_ = await async_subtensor.extrinsics.commit_weights( + wallet=alice_wallet, + netuid=netuid, + salt=salt_, + uids=weight_uids_, + weights=weight_vals_, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + return success_, message_ + + max_retries = 3 + timeout = 60.0 + sleep = 0.25 if async_subtensor.chain.is_fast_blocks() else 12.0 + + for attempt in range(1, max_retries + 1): + try: + start_nonce = await async_subtensor.substrate.get_account_nonce( + alice_wallet.hotkey.ss58_address + ) + + result = await send_commit_() + + start = time.time() + while (time.time() - start) < timeout: + current_nonce = await async_subtensor.substrate.get_account_nonce( + alice_wallet.hotkey.ss58_address + ) + + if current_nonce != start_nonce: + logging.console.info( + f"✅ Nonce changed from {start_nonce} to {current_nonce}" + ) + return result + logging.console.info( + f"⏳ Waiting for nonce increment. Current: {current_nonce}" + ) + time.sleep(sleep) + except Exception as e: + raise e + raise Exception(f"Failed to commit weights after {max_retries} attempts.") + + # Send some number of commit weights + AMOUNT_OF_COMMIT_WEIGHTS = 3 + for call in range(AMOUNT_OF_COMMIT_WEIGHTS): + weight_uids, weight_vals, salt = get_weights_and_salt(call) + + await send_commit(salt, weight_uids, weight_vals) + + # let's wait for 3 (12 fast blocks) seconds between transactions, next block for non-fast-blocks + waiting_block = ( + (await async_subtensor.block + 12) + if await async_subtensor.chain.is_fast_blocks() + else None + ) + await async_subtensor.wait_for_block(waiting_block) + + logging.console.info( + f"[orange]Nonce after third commit_weights: " + f"{await async_subtensor.substrate.get_account_next_index(alice_wallet.hotkey.ss58_address)}[/orange]" + ) + + # Wait a few blocks + waiting_block = ( + (await async_subtensor.block + await async_subtensor.subnets.tempo(netuid) * 2) + if await async_subtensor.chain.is_fast_blocks() + else None + ) + await async_subtensor.wait_for_block(waiting_block) + + # Query the WeightCommits storage map for all three salts + weight_commits = await async_subtensor.query_module( + module="SubtensorModule", + name="WeightCommits", + params=[netuid, alice_wallet.hotkey.ss58_address], + ) + # Assert that the committed weights are set correctly + assert weight_commits.value is not None, "Weight commit not found in storage" + commit_hash, commit_block, reveal_block, expire_block = weight_commits.value[0] + assert commit_block > 0, f"Invalid block number: {commit_block}" + + # Check for three commits in the WeightCommits storage map + assert len(weight_commits.value) == AMOUNT_OF_COMMIT_WEIGHTS, ( + "Expected exact list of weight commits" + ) + logging.console.success("Passed `test_commit_and_reveal_weights_async` test.") From 8aedf60eac1d0f79e0939368010b35194a510523 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 01:09:21 -0700 Subject: [PATCH 07/86] improve `tests/e2e_tests/test_commitment.py` --- tests/e2e_tests/test_commitment.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index a6a33f98c2..338b39d851 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -2,7 +2,10 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor import logging -from tests.e2e_tests.utils.chain_interactions import sudo_set_admin_utils +from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_admin_utils, + sudo_set_admin_utils, +) from tests.e2e_tests.utils.e2e_test_utils import ( wait_to_start_call, async_wait_to_start_call, @@ -11,7 +14,7 @@ logging.set_trace() -def test_commitment(local_chain, subtensor, alice_wallet, dave_wallet): +def test_commitment(subtensor, alice_wallet, dave_wallet): dave_subnet_netuid = 2 assert subtensor.register_subnet(dave_wallet, True, True) assert subtensor.subnet_exists(dave_subnet_netuid), ( @@ -45,14 +48,14 @@ def test_commitment(local_chain, subtensor, alice_wallet, dave_wallet): ) assert subtensor.set_commitment( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", ) status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_module="Commitments", call_function="set_max_space", call_params={ @@ -68,7 +71,7 @@ def test_commitment(local_chain, subtensor, alice_wallet, dave_wallet): match="SpaceLimitExceeded", ): subtensor.set_commitment( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!1", ) @@ -87,9 +90,7 @@ def test_commitment(local_chain, subtensor, alice_wallet, dave_wallet): @pytest.mark.asyncio -async def test_commitment_async( - local_chain, async_subtensor, alice_wallet, dave_wallet -): +async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): dave_subnet_netuid = 2 assert await async_subtensor.register_subnet(dave_wallet) assert await async_subtensor.subnet_exists(dave_subnet_netuid), ( @@ -131,9 +132,9 @@ async def test_commitment_async( data="Hello World!", ) - status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, call_module="Commitments", call_function="set_max_space", call_params={ From e75fa81a43e32af210a1384871abec1687829d5f Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:07:50 -0700 Subject: [PATCH 08/86] update `SubtensorApi` --- bittensor/core/subtensor_api/subnets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index cfdee525b8..6106f2e04f 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -12,6 +12,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.blocks_since_last_step = subtensor.blocks_since_last_step self.blocks_since_last_update = subtensor.blocks_since_last_update self.bonds = subtensor.bonds + self.burned_register = subtensor.burned_register self.commit_reveal_enabled = subtensor.commit_reveal_enabled self.difficulty = subtensor.difficulty self.get_all_subnets_info = subtensor.get_all_subnets_info From b85d6f8f3a1c0b80c295283695e62d3d10e950a6 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:11:19 -0700 Subject: [PATCH 09/86] add async functions to `chain_interactions.py` --- tests/e2e_tests/utils/chain_interactions.py | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 18422f1565..252288d276 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -419,6 +419,37 @@ def set_identity( ) +async def async_set_identity( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + name="", + url="", + github_repo="", + image="", + discord="", + description="", + additional="", +): + return await subtensor.sign_and_send_extrinsic( + await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_identity", + call_params={ + "name": name, + "url": url, + "github_repo": github_repo, + "image": image, + "discord": discord, + "description": description, + "additional": additional, + }, + ), + wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + def propose(subtensor, wallet, proposal, duration): return subtensor.sign_and_send_extrinsic( subtensor.substrate.compose_call( @@ -436,6 +467,28 @@ def propose(subtensor, wallet, proposal, duration): ) +async def async_propose( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + proposal, + duration, +): + return await subtensor.sign_and_send_extrinsic( + call=await subtensor.substrate.compose_call( + call_module="Triumvirate", + call_function="propose", + call_params={ + "proposal": proposal, + "length_bound": len(proposal.data), + "duration": duration, + }, + ), + wallet=wallet, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + + def vote( subtensor, wallet, @@ -459,3 +512,28 @@ def vote( wait_for_inclusion=True, wait_for_finalization=True, ) + + +async def async_vote( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + hotkey, + proposal, + index, + approve, +): + return await subtensor.sign_and_send_extrinsic( + call=await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="vote", + call_params={ + "approve": approve, + "hotkey": hotkey, + "index": index, + "proposal": proposal, + }, + ), + wallet=wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) From 97ad3d8770c864a30f5c4ab325025e0620e384a7 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:11:34 -0700 Subject: [PATCH 10/86] add async tests to `tests/e2e_tests/test_delegate.py` --- tests/e2e_tests/test_delegate.py | 741 +++++++++++++++++++++++++++---- 1 file changed, 649 insertions(+), 92 deletions(-) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 949b1324be..1b757211aa 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -1,18 +1,31 @@ import pytest -import bittensor from bittensor.core.chain_data.chain_identity import ChainIdentity from bittensor.core.chain_data.delegate_info import DelegatedInfo, DelegateInfo from bittensor.core.chain_data.proposal_vote_data import ProposalVoteData +from bittensor.core.errors import ( + DelegateTakeTooHigh, + DelegateTxRateLimitExceeded, + HotKeyAccountNotExists, + NonAssociatedColdKey, +) from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( + async_propose, + async_set_identity, + async_sudo_set_admin_utils, + async_vote, get_dynamic_balance, propose, set_identity, sudo_set_admin_utils, vote, ) -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) from tests.helpers.helpers import CloseInValue DEFAULT_DELEGATE_TAKE = 0.179995422293431 @@ -24,28 +37,90 @@ def test_identity(subtensor, alice_wallet, bob_wallet): - Check Delegate's default identity - Update Delegate's identity """ + logging.console.info("Testing [green]test_identity_async[/green].") - identity = subtensor.query_identity(alice_wallet.coldkeypub.ss58_address) - + identity = subtensor.neurons.query_identity(alice_wallet.coldkeypub.ss58_address) assert identity is None - identities = subtensor.get_delegate_identities() - + identities = subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address not in identities - subtensor.root_register( + subtensor.extrinsics.root_register( alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ) - identities = subtensor.get_delegate_identities() - + identities = subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address not in identities success, error = set_identity( - subtensor, + subtensor=subtensor, + wallet=alice_wallet, + name="Alice", + url="https://www.example.com", + github_repo="https://github.com/opentensor/bittensor", + description="Local Chain", + ) + assert error == "" + assert success is True + + identity = subtensor.neurons.query_identity(alice_wallet.coldkeypub.ss58_address) + assert identity == ChainIdentity( + additional="", + description="Local Chain", + discord="", + github="https://github.com/opentensor/bittensor", + image="", + name="Alice", + url="https://www.example.com", + ) + + identities = subtensor.delegates.get_delegate_identities() + assert alice_wallet.coldkey.ss58_address in identities + + identity = identities[alice_wallet.coldkey.ss58_address] + assert identity == ChainIdentity( + additional="", + description="Local Chain", + discord="", + github="https://github.com/opentensor/bittensor", + image="", + name="Alice", + url="https://www.example.com", + ) + logging.console.success("Test [green]test_identity_async[/green] passed.") + + +@pytest.mark.asyncio +async def test_identity_async(async_subtensor, alice_wallet, bob_wallet): + """ + Async tests: + - Check Delegate's default identity + - Update Delegate's identity + """ + logging.console.info("Testing [green]test_identity_async_async[/green].") + + identity = await async_subtensor.neurons.query_identity( + alice_wallet.coldkeypub.ss58_address + ) + assert identity is None + + identities = await async_subtensor.delegates.get_delegate_identities() + assert alice_wallet.coldkey.ss58_address not in identities + + await async_subtensor.extrinsics.root_register( alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + identities = await async_subtensor.delegates.get_delegate_identities() + assert alice_wallet.coldkey.ss58_address not in identities + + success, error = await async_set_identity( + subtensor=async_subtensor, + wallet=alice_wallet, name="Alice", url="https://www.example.com", github_repo="https://github.com/opentensor/bittensor", @@ -55,7 +130,9 @@ def test_identity(subtensor, alice_wallet, bob_wallet): assert error == "" assert success is True - identity = subtensor.query_identity(alice_wallet.coldkeypub.ss58_address) + identity = await async_subtensor.neurons.query_identity( + alice_wallet.coldkeypub.ss58_address + ) assert identity == ChainIdentity( additional="", @@ -67,12 +144,10 @@ def test_identity(subtensor, alice_wallet, bob_wallet): url="https://www.example.com", ) - identities = subtensor.get_delegate_identities() - + identities = await async_subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address in identities identity = identities[alice_wallet.coldkey.ss58_address] - assert identity == ChainIdentity( additional="", description="Local Chain", @@ -82,9 +157,10 @@ def test_identity(subtensor, alice_wallet, bob_wallet): name="Alice", url="https://www.example.com", ) + logging.console.success("Test [green]test_identity_async[/green] passed.") -def test_change_take(local_chain, subtensor, alice_wallet, bob_wallet): +def test_change_take(subtensor, alice_wallet, bob_wallet): """ Tests: - Get default Delegate's take once registered in root subnet @@ -92,86 +168,178 @@ def test_change_take(local_chain, subtensor, alice_wallet, bob_wallet): - Try corner cases (increase/decrease beyond allowed min/max) """ - with pytest.raises(bittensor.HotKeyAccountNotExists): - subtensor.set_delegate_take( + logging.console.info("Testing [green]test_change_take[/green].") + with pytest.raises(HotKeyAccountNotExists): + subtensor.delegates.set_delegate_take( alice_wallet, alice_wallet.hotkey.ss58_address, 0.1, raise_error=True, ) - subtensor.root_register( + subtensor.extrinsics.root_register( alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ) - take = subtensor.get_delegate_take(alice_wallet.hotkey.ss58_address) - + take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == DEFAULT_DELEGATE_TAKE - with pytest.raises(bittensor.NonAssociatedColdKey): - subtensor.set_delegate_take( + with pytest.raises(NonAssociatedColdKey): + subtensor.delegates.set_delegate_take( bob_wallet, alice_wallet.hotkey.ss58_address, 0.1, raise_error=True, ) - with pytest.raises(bittensor.DelegateTakeTooHigh): - subtensor.set_delegate_take( + with pytest.raises(DelegateTakeTooHigh): + subtensor.delegates.set_delegate_take( alice_wallet, alice_wallet.hotkey.ss58_address, 0.5, raise_error=True, ) - subtensor.set_delegate_take( + subtensor.delegates.set_delegate_take( alice_wallet, alice_wallet.hotkey.ss58_address, 0.1, raise_error=True, ) - take = subtensor.get_delegate_take(alice_wallet.hotkey.ss58_address) - + take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == 0.09999237048905166 - with pytest.raises(bittensor.DelegateTxRateLimitExceeded): - subtensor.set_delegate_take( + with pytest.raises(DelegateTxRateLimitExceeded): + subtensor.delegates.set_delegate_take( alice_wallet, alice_wallet.hotkey.ss58_address, 0.15, raise_error=True, ) - take = subtensor.get_delegate_take(alice_wallet.hotkey.ss58_address) - + take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == 0.09999237048905166 sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tx_delegate_take_rate_limit", call_params={ "tx_rate_limit": 0, }, ) - subtensor.set_delegate_take( + subtensor.delegates.set_delegate_take( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + take=0.15, + raise_error=True, + ) + + take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) + assert take == 0.14999618524452582 + + logging.console.success("Test [green]test_change_take[/green] passed.") + + +@pytest.mark.asyncio +async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): + """ + Async tests: + - Get default Delegate's take once registered in root subnet + - Increase and decreased Delegate's take + - Try corner cases (increase/decrease beyond allowed min/max) + """ + + logging.console.info("Testing [green]test_change_take_async[/green].") + with pytest.raises(HotKeyAccountNotExists): + await async_subtensor.delegates.set_delegate_take( + alice_wallet, + alice_wallet.hotkey.ss58_address, + 0.1, + raise_error=True, + ) + + await async_subtensor.extrinsics.root_register( + alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + take = await async_subtensor.delegates.get_delegate_take( + alice_wallet.hotkey.ss58_address + ) + assert take == DEFAULT_DELEGATE_TAKE + + with pytest.raises(NonAssociatedColdKey): + await async_subtensor.delegates.set_delegate_take( + bob_wallet, + alice_wallet.hotkey.ss58_address, + 0.1, + raise_error=True, + ) + + with pytest.raises(DelegateTakeTooHigh): + await async_subtensor.delegates.set_delegate_take( + alice_wallet, + alice_wallet.hotkey.ss58_address, + 0.5, + raise_error=True, + ) + + await async_subtensor.delegates.set_delegate_take( alice_wallet, alice_wallet.hotkey.ss58_address, - 0.15, + 0.1, raise_error=True, ) - take = subtensor.get_delegate_take(alice_wallet.hotkey.ss58_address) + take = await async_subtensor.delegates.get_delegate_take( + alice_wallet.hotkey.ss58_address + ) + assert take == 0.09999237048905166 + + with pytest.raises(DelegateTxRateLimitExceeded): + await async_subtensor.delegates.set_delegate_take( + alice_wallet, + alice_wallet.hotkey.ss58_address, + 0.15, + raise_error=True, + ) + + take = await async_subtensor.delegates.get_delegate_take( + alice_wallet.hotkey.ss58_address + ) + assert take == 0.09999237048905166 + + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tx_delegate_take_rate_limit", + call_params={ + "tx_rate_limit": 0, + }, + ) + + await async_subtensor.delegates.set_delegate_take( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + take=0.15, + raise_error=True, + ) + take = await async_subtensor.delegates.get_delegate_take( + alice_wallet.hotkey.ss58_address + ) assert take == 0.14999618524452582 + logging.console.success("Test [green]test_change_take_async[/green] passed.") -@pytest.mark.asyncio -async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet): + +def test_delegates(subtensor, alice_wallet, bob_wallet): """ Tests: - Check default Delegates @@ -179,30 +347,48 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet): - Check if Hotkey is a Delegate - Nominator Staking """ + logging.console.info("Testing [green]test_delegates[/green].") - assert subtensor.get_delegates() == [] - assert subtensor.get_delegated(alice_wallet.coldkey.ss58_address) == [] - assert subtensor.get_delegate_by_hotkey(alice_wallet.hotkey.ss58_address) is None - assert subtensor.get_delegate_by_hotkey(bob_wallet.hotkey.ss58_address) is None + assert subtensor.delegates.get_delegates() == [] + assert subtensor.delegates.get_delegated(alice_wallet.coldkey.ss58_address) == [] + assert ( + subtensor.delegates.get_delegate_by_hotkey(alice_wallet.hotkey.ss58_address) + is None + ) + assert ( + subtensor.delegates.get_delegate_by_hotkey(bob_wallet.hotkey.ss58_address) + is None + ) - assert subtensor.is_hotkey_delegate(alice_wallet.hotkey.ss58_address) is False - assert subtensor.is_hotkey_delegate(bob_wallet.hotkey.ss58_address) is False + assert ( + subtensor.delegates.is_hotkey_delegate(alice_wallet.hotkey.ss58_address) + is False + ) + assert ( + subtensor.delegates.is_hotkey_delegate(bob_wallet.hotkey.ss58_address) is False + ) - subtensor.root_register( + subtensor.extrinsics.root_register( alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ) - subtensor.root_register( + subtensor.extrinsics.root_register( bob_wallet, wait_for_inclusion=True, wait_for_finalization=True, ) - assert subtensor.is_hotkey_delegate(alice_wallet.hotkey.ss58_address) is True - assert subtensor.is_hotkey_delegate(bob_wallet.hotkey.ss58_address) is True + assert ( + subtensor.delegates.is_hotkey_delegate(alice_wallet.hotkey.ss58_address) is True + ) + assert ( + subtensor.delegates.is_hotkey_delegate(bob_wallet.hotkey.ss58_address) is True + ) - alice_delegate = subtensor.get_delegate_by_hotkey(alice_wallet.hotkey.ss58_address) + alice_delegate = subtensor.delegates.get_delegate_by_hotkey( + alice_wallet.hotkey.ss58_address + ) assert alice_delegate == DelegateInfo( hotkey_ss58=alice_wallet.hotkey.ss58_address, @@ -216,7 +402,9 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet): nominators={}, ) - bob_delegate = subtensor.get_delegate_by_hotkey(bob_wallet.hotkey.ss58_address) + bob_delegate = subtensor.delegates.get_delegate_by_hotkey( + bob_wallet.hotkey.ss58_address + ) assert bob_delegate == DelegateInfo( hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -230,22 +418,22 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet): nominators={}, ) - delegates = subtensor.get_delegates() + delegates = subtensor.delegates.get_delegates() assert delegates == [ bob_delegate, alice_delegate, ] - assert subtensor.get_delegated(bob_wallet.coldkey.ss58_address) == [] + assert subtensor.delegates.get_delegated(bob_wallet.coldkey.ss58_address) == [] - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 set_tempo = 10 # Register a subnet, netuid 2 - assert subtensor.register_subnet(alice_wallet), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" # Verify subnet created successfully - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -254,17 +442,17 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet): # set the same tempo for both type of nodes (fast and non-fast blocks) assert ( sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={"netuid": alice_subnet_netuid, "tempo": set_tempo}, )[0] is True ) - subtensor.add_stake( - bob_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, amount=Balance.from_tao(10_000), wait_for_inclusion=True, @@ -274,7 +462,7 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet): # let chain update validator_permits subtensor.wait_for_block(subtensor.block + set_tempo + 1) - bob_delegated = subtensor.get_delegated(bob_wallet.coldkey.ss58_address) + bob_delegated = subtensor.delegates.get_delegated(bob_wallet.coldkey.ss58_address) assert bob_delegated == [ DelegatedInfo( hotkey_ss58=alice_wallet.hotkey.ss58_address, @@ -290,12 +478,179 @@ async def test_delegates(local_chain, subtensor, alice_wallet, bob_wallet): stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid), ), ] - bittensor.logging.console.success("Test [green]test_delegates[/green] passed.") + logging.console.success("Test [green]test_delegates[/green] passed.") -def test_nominator_min_required_stake( - local_chain, subtensor, alice_wallet, bob_wallet, dave_wallet -): +@pytest.mark.asyncio +async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): + """ + Tests: + - Check default Delegates + - Register Delegates + - Check if Hotkey is a Delegate + - Nominator Staking + """ + logging.console.info("Testing [green]test_delegates_async[/green].") + + assert await async_subtensor.delegates.get_delegates() == [] + assert ( + await async_subtensor.delegates.get_delegated(alice_wallet.coldkey.ss58_address) + == [] + ) + assert ( + await async_subtensor.delegates.get_delegate_by_hotkey( + alice_wallet.hotkey.ss58_address + ) + is None + ) + assert ( + await async_subtensor.delegates.get_delegate_by_hotkey( + bob_wallet.hotkey.ss58_address + ) + is None + ) + + assert ( + await async_subtensor.delegates.is_hotkey_delegate( + alice_wallet.hotkey.ss58_address + ) + is False + ) + assert ( + await async_subtensor.delegates.is_hotkey_delegate( + bob_wallet.hotkey.ss58_address + ) + is False + ) + + await async_subtensor.extrinsics.root_register( + alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + await async_subtensor.extrinsics.root_register( + bob_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert ( + await async_subtensor.delegates.is_hotkey_delegate( + alice_wallet.hotkey.ss58_address + ) + is True + ) + assert ( + await async_subtensor.delegates.is_hotkey_delegate( + bob_wallet.hotkey.ss58_address + ) + is True + ) + + alice_delegate = await async_subtensor.delegates.get_delegate_by_hotkey( + alice_wallet.hotkey.ss58_address + ) + + assert alice_delegate == DelegateInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + owner_ss58=alice_wallet.coldkey.ss58_address, + take=DEFAULT_DELEGATE_TAKE, + validator_permits=[], + registrations=[0], + return_per_1000=Balance(0), + total_daily_return=Balance(0), + total_stake={}, + nominators={}, + ) + + bob_delegate = await async_subtensor.delegates.get_delegate_by_hotkey( + bob_wallet.hotkey.ss58_address + ) + + assert bob_delegate == DelegateInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + owner_ss58=bob_wallet.coldkey.ss58_address, + take=DEFAULT_DELEGATE_TAKE, + validator_permits=[], + registrations=[0], + return_per_1000=Balance(0), + total_daily_return=Balance(0), + total_stake={}, + nominators={}, + ) + + delegates = await async_subtensor.delegates.get_delegates() + + assert delegates == [ + bob_delegate, + alice_delegate, + ] + + assert ( + await async_subtensor.delegates.get_delegated(bob_wallet.coldkey.ss58_address) + == [] + ) + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + set_tempo = 10 + # Register a subnet, netuid 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Subnet wasn't created" + ) + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + # set the same tempo for both type of nodes (fast and non-fast blocks) + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": alice_subnet_netuid, "tempo": set_tempo}, + ) + )[0] is True + + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(10_000), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # let chain update validator_permits + await async_subtensor.wait_for_block(await async_subtensor.block + set_tempo + 1) + + bob_delegated = await async_subtensor.delegates.get_delegated( + bob_wallet.coldkey.ss58_address + ) + assert bob_delegated == [ + DelegatedInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + owner_ss58=alice_wallet.coldkey.ss58_address, + take=DEFAULT_DELEGATE_TAKE, + validator_permits=[alice_subnet_netuid], + registrations=[0, alice_subnet_netuid], + return_per_1000=Balance(0), + total_daily_return=get_dynamic_balance( + bob_delegated[0].total_daily_return.rao + ), + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid), + ), + ] + logging.console.success("Test [green]test_delegates_async[/green] passed.") + + +def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): """ Tests: - Check default NominatorMinRequiredStake @@ -303,42 +658,41 @@ def test_nominator_min_required_stake( - Update NominatorMinRequiredStake - Check Nominator is removed """ + logging.console.info("Testing [green]test_delegates_async[/green].") alice_subnet_netuid = subtensor.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.register_subnet( + assert subtensor.subnets.register_subnet( alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ), "Subnet wasn't created" # Verify subnet created successfully - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - minimum_required_stake = subtensor.get_minimum_required_stake() - + minimum_required_stake = subtensor.staking.get_minimum_required_stake() assert minimum_required_stake == Balance(0) - subtensor.burned_register( + subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - subtensor.burned_register( + subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - success = subtensor.add_stake( + success = subtensor.staking.add_stake( wallet=dave_wallet, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -346,20 +700,18 @@ def test_nominator_min_required_stake( wait_for_inclusion=True, wait_for_finalization=True, ) - assert success is True - stake = subtensor.get_stake( + stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, ) - assert stake > 0 # this will trigger clear_small_nominations sudo_set_admin_utils( - substrate=local_chain, + substrate=subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_nominator_min_required_stake", call_params={ @@ -367,18 +719,111 @@ def test_nominator_min_required_stake( }, ) - minimum_required_stake = subtensor.get_minimum_required_stake() - + minimum_required_stake = subtensor.staking.get_minimum_required_stake() assert minimum_required_stake == Balance.from_tao(100_000) - stake = subtensor.get_stake( + stake = subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + assert stake == Balance(0) + + logging.console.success( + "Test [green]test_nominator_min_required_stake[/green] passed." + ) + + +@pytest.mark.asyncio +async def test_nominator_min_required_stake_async( + async_subtensor, alice_wallet, bob_wallet, dave_wallet +): + """ + Async tests: + - Check default NominatorMinRequiredStake + - Add Stake to Nominate from Dave to Bob + - Update NominatorMinRequiredStake + - Check Nominator is removed + """ + logging.console.info( + "Testing [green]test_nominator_min_required_stake_async[/green]." + ) + + alice_subnet_netuid = await async_subtensor.get_total_subnets() # 2 + + # Register a subnet, netuid 2 + assert await async_subtensor.subnets.register_subnet( + alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ), "Subnet wasn't created" + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + minimum_required_stake = await async_subtensor.staking.get_minimum_required_stake() + assert minimum_required_stake == Balance(0) + + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + success = await async_subtensor.staking.add_stake( + wallet=dave_wallet, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1000), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success is True + + stake = await async_subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, ) + assert stake > 0 + + # this will trigger clear_small_nominations + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_nominator_min_required_stake", + call_params={ + "min_stake": "100000000000000", + }, + ) + minimum_required_stake = await async_subtensor.staking.get_minimum_required_stake() + assert minimum_required_stake == Balance.from_tao(100_000) + + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) assert stake == Balance(0) + logging.console.success( + "Test [green]test_nominator_min_required_stake_async[/green] passed." + ) + def test_get_vote_data(subtensor, alice_wallet): """ @@ -388,8 +833,11 @@ def test_get_vote_data(subtensor, alice_wallet): - Votes - Checks Proposal is updated """ + logging.console.info("Testing [green]test_get_vote_data[/green].") - subtensor.root_register(alice_wallet) + assert subtensor.extrinsics.root_register(alice_wallet), ( + "Can not register Alice in root SN." + ) proposals = subtensor.query_map( "Triumvirate", @@ -400,8 +848,8 @@ def test_get_vote_data(subtensor, alice_wallet): assert proposals.records == [] success, error = propose( - subtensor, - alice_wallet, + subtensor=subtensor, + wallet=alice_wallet, proposal=subtensor.substrate.compose_call( call_module="Triumvirate", call_function="set_members", @@ -418,8 +866,8 @@ def test_get_vote_data(subtensor, alice_wallet): assert success is True proposals = subtensor.query_map( - "Triumvirate", - "ProposalOf", + module="Triumvirate", + name="ProposalOf", params=[], ) proposals = { @@ -443,7 +891,7 @@ def test_get_vote_data(subtensor, alice_wallet): proposal_hash = list(proposals.keys())[0] proposal_hash = f"0x{proposal_hash.hex()}" - proposal = subtensor.get_vote_data( + proposal = subtensor.chain.get_vote_data( proposal_hash, ) @@ -456,10 +904,10 @@ def test_get_vote_data(subtensor, alice_wallet): ) success, error = vote( - subtensor, - alice_wallet, - alice_wallet.hotkey.ss58_address, - proposal_hash, + subtensor=subtensor, + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + proposal=proposal_hash, index=0, approve=True, ) @@ -467,8 +915,8 @@ def test_get_vote_data(subtensor, alice_wallet): assert error == "" assert success is True - proposal = subtensor.get_vote_data( - proposal_hash, + proposal = subtensor.chain.get_vote_data( + proposal_hash=proposal_hash, ) assert proposal == ProposalVoteData( @@ -480,3 +928,112 @@ def test_get_vote_data(subtensor, alice_wallet): nays=[], threshold=3, ) + logging.console.success("Test [green]test_get_vote_data[/green] passed.") + + +@pytest.mark.asyncio +async def test_get_vote_data_async(async_subtensor, alice_wallet): + """ + Async tests: + - Sends Propose + - Checks existing Proposals + - Votes + - Checks Proposal is updated + """ + logging.console.info("Testing [green]test_get_vote_data_async[/green].") + + assert await async_subtensor.extrinsics.root_register(alice_wallet), ( + "Can not register Alice in root SN." + ) + + proposals = await async_subtensor.query_map( + "Triumvirate", + "ProposalOf", + params=[], + ) + + assert proposals.records == [] + + success, error = await async_propose( + subtensor=async_subtensor, + wallet=alice_wallet, + proposal=await async_subtensor.substrate.compose_call( + call_module="Triumvirate", + call_function="set_members", + call_params={ + "new_members": [], + "prime": None, + "old_count": 0, + }, + ), + duration=1_000_000, + ) + + assert error == "" + assert success is True + + proposals = await async_subtensor.query_map( + module="Triumvirate", + name="ProposalOf", + params=[], + ) + proposals = { + bytes(proposal_hash[0]): proposal.value + async for proposal_hash, proposal in proposals + } + + assert list(proposals.values()) == [ + { + "Triumvirate": ( + { + "set_members": { + "new_members": (), + "prime": None, + "old_count": 0, + }, + }, + ), + }, + ] + + proposal_hash = list(proposals.keys())[0] + proposal_hash = f"0x{proposal_hash.hex()}" + + proposal = await async_subtensor.chain.get_vote_data( + proposal_hash, + ) + + assert proposal == ProposalVoteData( + ayes=[], + end=CloseInValue(1_000_000, await async_subtensor.block), + index=0, + nays=[], + threshold=3, + ) + + success, error = await async_vote( + subtensor=async_subtensor, + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + proposal=proposal_hash, + index=0, + approve=True, + ) + + assert error == "" + assert success is True + + proposal = await async_subtensor.chain.get_vote_data( + proposal_hash=proposal_hash, + ) + + assert proposal == ProposalVoteData( + ayes=[ + alice_wallet.hotkey.ss58_address, + ], + end=CloseInValue(1_000_000, await async_subtensor.block), + index=0, + nays=[], + threshold=3, + ) + logging.console.success("Test [green]test_get_vote_data_async[/green] passed.") From ec3da56cadc905e0a3d6ed5bb54e9ea79a2ece7f Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:46:45 -0700 Subject: [PATCH 11/86] add async tests to `tests/e2e_tests/test_dendrite.py` --- tests/e2e_tests/test_dendrite.py | 191 +++++++++++++++++++++++++++---- 1 file changed, 169 insertions(+), 22 deletions(-) diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index bc439d2da3..841e349557 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -5,14 +5,19 @@ from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_admin_utils, + async_wait_epoch, sudo_set_admin_utils, wait_epoch, ) -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) @pytest.mark.asyncio -async def test_dendrite(local_chain, subtensor, templates, alice_wallet, bob_wallet): +async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): """ Test the Dendrite mechanism @@ -25,31 +30,38 @@ async def test_dendrite(local_chain, subtensor, templates, alice_wallet, bob_wal Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing `test_dendrite`.") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 - logging.console.info("Testing test_dendrite") + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.register_subnet(alice_wallet, True, True), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( + "Subnet wasn't created." + ) # Verify subnet created successfully - assert subtensor.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully." ) - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid), ( + "Subnet wasn't started." + ) + assert subtensor.subnets.is_subnet_active(alice_subnet_netuid), ( + "Subnet is not active." + ) # Make sure Alice is Top Validator - assert subtensor.add_stake( - alice_wallet, + assert subtensor.staking.add_stake( + wallet=alice_wallet, netuid=alice_subnet_netuid, amount=Balance.from_tao(1), ) # update max_allowed_validators so only one neuron can get validator_permit assert sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_max_allowed_validators", call_params={ "netuid": alice_subnet_netuid, @@ -59,24 +71,23 @@ async def test_dendrite(local_chain, subtensor, templates, alice_wallet, bob_wal # update weights_set_rate_limit for fast-blocks status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={ "netuid": alice_subnet_netuid, "weights_set_rate_limit": 10, }, ) - assert error is None assert status is True # Register Bob to the network - assert subtensor.burned_register(bob_wallet, alice_subnet_netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( "Unable to register Bob as a neuron" ) - metagraph = subtensor.metagraph(alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) # Assert neurons are Alice and Bob assert len(metagraph.neurons) == 2 @@ -94,16 +105,18 @@ async def test_dendrite(local_chain, subtensor, templates, alice_wallet, bob_wal # Stake to become to top neuron after the first epoch tao = Balance.from_tao(10_000) - alpha, _ = subtensor.subnet(alice_subnet_netuid).tao_to_alpha_with_slippage(tao) + alpha, _ = subtensor.subnets.subnet(alice_subnet_netuid).tao_to_alpha_with_slippage( + tao + ) - assert subtensor.add_stake( + assert subtensor.staking.add_stake( bob_wallet, netuid=alice_subnet_netuid, amount=tao, ) # Refresh metagraph - metagraph = subtensor.metagraph(alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) bob_neuron = metagraph.neurons[1] # Assert alpha is close to stake equivalent @@ -121,7 +134,141 @@ async def test_dendrite(local_chain, subtensor, templates, alice_wallet, bob_wal await wait_epoch(subtensor, netuid=alice_subnet_netuid) # Refresh metagraph - metagraph = subtensor.metagraph(alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) + + # Refresh validator neuron + updated_neuron = metagraph.neurons[1] + + assert len(metagraph.neurons) == 2 + assert updated_neuron.active is True + assert updated_neuron.validator_permit is True + assert updated_neuron.hotkey == bob_wallet.hotkey.ss58_address + assert updated_neuron.coldkey == bob_wallet.coldkey.ss58_address + assert updated_neuron.pruning_score != 0 + + logging.console.info("✅ Passed `test_dendrite`") + + +@pytest.mark.asyncio +async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wallet): + """ + Test the Dendrite mechanism + + Steps: + 1. Register a subnet through Alice + 2. Register Bob as a validator + 3. Add stake to Bob and ensure neuron is not a validator yet + 4. Run Bob as a validator and wait epoch + 5. Ensure Bob's neuron has all correct attributes of a validator + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing `test_dendrite_async`.") + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Register a subnet, netuid 2 + assert await async_subtensor.subnets.register_subnet( + alice_wallet, wait_for_inclusion=True, wait_for_finalization=True + ), "Subnet wasn't created" + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ), "Subnet wasn't started." + + assert await async_subtensor.subnets.is_subnet_active(alice_subnet_netuid), ( + "Subnet is not active." + ) + + # Make sure Alice is Top Validator + assert await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1), + ) + + # update max_allowed_validators so only one neuron can get validator_permit + assert await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_max_allowed_validators", + call_params={ + "netuid": alice_subnet_netuid, + "max_allowed_validators": 1, + }, + ) + + # update weights_set_rate_limit for fast-blocks + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={ + "netuid": alice_subnet_netuid, + "weights_set_rate_limit": 10, + }, + ) + assert error is None + assert status is True + + # Register Bob to the network + assert await async_subtensor.subnets.burned_register( + bob_wallet, alice_subnet_netuid + ), "Unable to register Bob as a neuron" + + metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) + + # Assert neurons are Alice and Bob + assert len(metagraph.neurons) == 2 + + alice_neuron = metagraph.neurons[0] + assert alice_neuron.hotkey == alice_wallet.hotkey.ss58_address + assert alice_neuron.coldkey == alice_wallet.coldkey.ss58_address + + bob_neuron = metagraph.neurons[1] + assert bob_neuron.hotkey == bob_wallet.hotkey.ss58_address + assert bob_neuron.coldkey == bob_wallet.coldkey.ss58_address + + # Assert stake is 0 + assert bob_neuron.stake.tao == 0 + + # Stake to become to top neuron after the first epoch + tao = Balance.from_tao(10_000) + alpha, _ = ( + await async_subtensor.subnets.subnet(alice_subnet_netuid) + ).tao_to_alpha_with_slippage(tao) + + assert await async_subtensor.staking.add_stake( + bob_wallet, + netuid=alice_subnet_netuid, + amount=tao, + ) + + # Refresh metagraph + metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) + bob_neuron = metagraph.neurons[1] + + # Assert alpha is close to stake equivalent + assert 0.95 < bob_neuron.stake.rao / alpha.rao < 1.05 + + # Assert neuron is not a validator yet + assert bob_neuron.active is True + assert bob_neuron.validator_permit is False + assert bob_neuron.validator_trust == 0.0 + assert bob_neuron.pruning_score == 0 + + async with templates.validator(bob_wallet, alice_subnet_netuid): + await asyncio.sleep(5) # wait for 5 seconds for the Validator to process + + await async_wait_epoch(async_subtensor, netuid=alice_subnet_netuid) + + # Refresh metagraph + metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) # Refresh validator neuron updated_neuron = metagraph.neurons[1] @@ -133,4 +280,4 @@ async def test_dendrite(local_chain, subtensor, templates, alice_wallet, bob_wal assert updated_neuron.coldkey == bob_wallet.coldkey.ss58_address assert updated_neuron.pruning_score != 0 - logging.console.info("✅ Passed test_dendrite") + logging.console.info("✅ Passed `test_dendrite_async`") From 1d108502420a4b7bb36c7374b2bda0e7f46b9169 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 14:04:32 -0700 Subject: [PATCH 12/86] improve deps and version --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 71f245a935..40a7e7f85f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor" -version = "9.9.0" +version = "9.10.0" description = "Bittensor" readme = "README.md" authors = [ @@ -36,7 +36,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=1.0.0,<2.0.0", "bittensor-wallet>=4.0.0,<5.0", - "async-substrate-interface>=1.4.2" + "async-substrate-interface>=1.5.1" ] [project.optional-dependencies] From c47fa432e5a9fa4b8201d723e11b5540e0128b0a Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 14:11:17 -0700 Subject: [PATCH 13/86] CHANGELOG.md --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1201b090a0..0229e03163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 9.10.0 /2025-08-28 + +## What's Changed +* Fixes broken e2e tests by @thewhaleking in https://github.com/opentensor/bittensor/pull/3020 +* Add async crv4 e2e test by @basfroman in https://github.com/opentensor/bittensor/pull/3022 +* Use `TimelockedWeightCommits` instead of `CRV3WeightCommitsV2` by @basfroman in https://github.com/opentensor/bittensor/pull/3023 +* fix: reflect correct return types for get_delegated by @Arthurdw in https://github.com/opentensor/bittensor/pull/3016 +* Fix `flaky` e2e test (tests.e2e_tests.test_staking.test_safe_staking_scenarios) by @basfroman in https://github.com/opentensor/bittensor/pull/3025 +* Separation of test modules into separate text elements as independent matrix elements by @basfroman in https://github.com/opentensor/bittensor/pull/3027 +* Improve `move_stake` extrinsic (add `move_all_stake` parameter) by @basfroman in https://github.com/opentensor/bittensor/pull/3028 +* Fix tests related with disabled `sudo_set_commit_reveal_weights_enabled` by @basfroman in https://github.com/opentensor/bittensor/pull/3026 + + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.9.0...v9.10.0 + ## 9.9.0 /2025-08-11 ## What's Changed From c6d0db28e1d15ec2a48028cf7bb92e183e141eee Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 14:48:03 -0700 Subject: [PATCH 14/86] Update `SubtensorApi` --- bittensor/core/subtensor_api/wallets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bittensor/core/subtensor_api/wallets.py b/bittensor/core/subtensor_api/wallets.py index 7314a4fe39..9b3a3a058b 100644 --- a/bittensor/core/subtensor_api/wallets.py +++ b/bittensor/core/subtensor_api/wallets.py @@ -13,6 +13,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): ) self.is_hotkey_registered_any = subtensor.is_hotkey_registered_any self.is_hotkey_registered = subtensor.is_hotkey_registered + self.is_hotkey_registered_on_subnet = subtensor.is_hotkey_registered_on_subnet self.is_hotkey_delegate = subtensor.is_hotkey_delegate self.get_balance = subtensor.get_balance self.get_balances = subtensor.get_balances @@ -26,6 +27,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_minimum_required_stake = subtensor.get_minimum_required_stake self.get_netuids_for_hotkey = subtensor.get_netuids_for_hotkey self.get_owned_hotkeys = subtensor.get_owned_hotkeys + self.get_parents = subtensor.get_parents self.get_stake = subtensor.get_stake self.get_stake_add_fee = subtensor.get_stake_add_fee self.get_stake_for_coldkey = subtensor.get_stake_for_coldkey From e6e83d83c59794d3a1019117510cd3587d3c3a90 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 14:48:44 -0700 Subject: [PATCH 15/86] add async e2e tests in `tests/e2e_tests/test_hotkeys.py` --- tests/e2e_tests/test_hotkeys.py | 599 +++++++++++++++++++++++++++----- 1 file changed, 507 insertions(+), 92 deletions(-) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 5831e4bf27..47c2b94ce6 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -12,12 +12,17 @@ NonAssociatedColdKey, ) from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.chain_interactions import sudo_set_admin_utils -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call - +from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_admin_utils, + sudo_set_admin_utils, +) +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) -SET_CHILDREN_RATE_LIMIT = 15 -ROOT_COOLDOWN = 15 # blocks +SET_CHILDREN_RATE_LIMIT = 30 +ROOT_COOLDOWN = 50 # blocks def test_hotkeys(subtensor, alice_wallet, dave_wallet): @@ -26,9 +31,11 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): - Check if Hotkey exists - Check if Hotkey is registered """ - dave_subnet_netuid = subtensor.get_total_subnets() # 2 - assert subtensor.register_subnet(dave_wallet, True, True) - assert subtensor.subnet_exists(dave_subnet_netuid), ( + logging.console.info("Testing [green]test_hotkeys[/green].") + + dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(dave_wallet, True, True) + assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( f"Subnet #{dave_subnet_netuid} does not exist." ) @@ -38,34 +45,34 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): hotkey = alice_wallet.hotkey.ss58_address with pytest.raises(ValueError, match="Invalid checksum"): - subtensor.does_hotkey_exist("fake") + subtensor.wallets.does_hotkey_exist("fake") - assert subtensor.does_hotkey_exist(hotkey) is False - assert subtensor.get_hotkey_owner(hotkey) is None + assert subtensor.wallets.does_hotkey_exist(hotkey) is False + assert subtensor.wallets.get_hotkey_owner(hotkey) is None - assert subtensor.is_hotkey_registered(hotkey) is False - assert subtensor.is_hotkey_registered_any(hotkey) is False + assert subtensor.wallets.is_hotkey_registered(hotkey) is False + assert subtensor.wallets.is_hotkey_registered_any(hotkey) is False assert ( - subtensor.is_hotkey_registered_on_subnet( - hotkey, + subtensor.wallets.is_hotkey_registered_on_subnet( + hotkey_ss58=hotkey, netuid=dave_subnet_netuid, ) is False ) - subtensor.burned_register( + subtensor.subnets.burned_register( alice_wallet, netuid=dave_subnet_netuid, ) - assert subtensor.does_hotkey_exist(hotkey) is True - assert subtensor.get_hotkey_owner(hotkey) == coldkey + assert subtensor.wallets.does_hotkey_exist(hotkey) is True + assert subtensor.wallets.get_hotkey_owner(hotkey) == coldkey - assert subtensor.is_hotkey_registered(hotkey) is True - assert subtensor.is_hotkey_registered_any(hotkey) is True + assert subtensor.wallets.is_hotkey_registered(hotkey) is True + assert subtensor.wallets.is_hotkey_registered_any(hotkey) is True assert ( - subtensor.is_hotkey_registered_on_subnet( - hotkey, + subtensor.wallets.is_hotkey_registered_on_subnet( + hotkey_ss58=hotkey, netuid=dave_subnet_netuid, ) is True @@ -74,7 +81,64 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): @pytest.mark.asyncio -async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_wallet): +async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): + """ + Async tests: + - Check if Hotkey exists + - Check if Hotkey is registered + """ + logging.console.info("Testing [green]test_hotkeys_async[/green].") + + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + assert await async_subtensor.subnets.register_subnet(dave_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( + f"Subnet #{dave_subnet_netuid} does not exist." + ) + + assert await async_wait_to_start_call( + async_subtensor, dave_wallet, dave_subnet_netuid + ) + + coldkey = alice_wallet.coldkeypub.ss58_address + hotkey = alice_wallet.hotkey.ss58_address + + with pytest.raises(ValueError, match="Invalid checksum"): + await async_subtensor.wallets.does_hotkey_exist("fake") + + assert await async_subtensor.wallets.does_hotkey_exist(hotkey) is False + assert await async_subtensor.wallets.get_hotkey_owner(hotkey) is None + + assert await async_subtensor.wallets.is_hotkey_registered(hotkey) is False + assert await async_subtensor.wallets.is_hotkey_registered_any(hotkey) is False + assert ( + await async_subtensor.wallets.is_hotkey_registered_on_subnet( + hotkey_ss58=hotkey, + netuid=dave_subnet_netuid, + ) + is False + ) + + assert await async_subtensor.subnets.burned_register( + alice_wallet, + netuid=dave_subnet_netuid, + ) + + assert await async_subtensor.wallets.does_hotkey_exist(hotkey) is True + assert await async_subtensor.wallets.get_hotkey_owner(hotkey) == coldkey + + assert await async_subtensor.wallets.is_hotkey_registered(hotkey) is True + assert await async_subtensor.wallets.is_hotkey_registered_any(hotkey) is True + assert ( + await async_subtensor.wallets.is_hotkey_registered_on_subnet( + hotkey_ss58=hotkey, + netuid=dave_subnet_netuid, + ) + is True + ) + logging.console.success("✅ Test [green]test_hotkeys[/green] passed") + + +def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): """ Tests: - Get default children (empty list) @@ -86,7 +150,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w - Clear children list """ - dave_subnet_netuid = subtensor.get_total_subnets() # 2 + logging.console.info("Testing [green]test_children[/green].") + + dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 set_tempo = 10 # affect to non-fast-blocks mode # Set cooldown @@ -99,19 +165,19 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w == "Success with `root_set_pending_childkey_cooldown_extrinsic` response." ) - assert subtensor.register_subnet(dave_wallet, True, True) - assert subtensor.subnet_exists(dave_subnet_netuid), ( + assert subtensor.subnets.register_subnet(dave_wallet, True, True) + assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( f"Subnet #{dave_subnet_netuid} does not exist." ) assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) # set the same tempo for both type of nodes (to avoid tests timeout) - if not subtensor.is_fast_blocks(): + if not subtensor.chain.is_fast_blocks(): assert ( sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={"netuid": dave_subnet_netuid, "tempo": set_tempo}, )[0] @@ -120,8 +186,8 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w assert ( sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tx_rate_limit", call_params={"tx_rate_limit": 0}, )[0] @@ -129,45 +195,46 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ) with pytest.raises(RegistrationNotPermittedOnRootSubnet): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=0, children=[], raise_error=True, ) with pytest.raises(NonAssociatedColdKey): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=1, children=[], raise_error=True, ) with pytest.raises(SubNetworkDoesNotExist): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=3, children=[], raise_error=True, ) - subtensor.burned_register( - alice_wallet, + assert subtensor.subnets.burned_register( + wallet=alice_wallet, netuid=dave_subnet_netuid, ) logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") - subtensor.burned_register( - bob_wallet, + + assert subtensor.subnets.burned_register( + wallet=bob_wallet, netuid=dave_subnet_netuid, ) logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") - success, children, error = subtensor.get_children( - alice_wallet.hotkey.ss58_address, + success, children, error = subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -176,9 +243,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w assert children == [] with pytest.raises(InvalidChild): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -190,9 +257,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ) with pytest.raises(TooManyChildren): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -205,9 +272,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ) with pytest.raises(ProportionOverflow): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -223,9 +290,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ) with pytest.raises(DuplicateChild): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -240,9 +307,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w raise_error=True, ) - success, error = subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + success, message = subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -251,16 +318,18 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ), ], raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, ) - assert error == "Success with `set_children_extrinsic` response." + assert message == "Success with `set_children_extrinsic` response." assert success is True - set_children_block = subtensor.get_current_block() + set_children_block = subtensor.block # children not set yet (have to wait cool-down period) - success, children, error = subtensor.get_children( - alice_wallet.hotkey.ss58_address, + success, children, error = subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, block=set_children_block, netuid=dave_subnet_netuid, ) @@ -270,18 +339,22 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w assert error == "" # children are in pending state - pending, cooldown = subtensor.get_children_pending( - alice_wallet.hotkey.ss58_address, + pending, cooldown = subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) + logging.console.info( + f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" + ) + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] # we use `*2` to ensure that the chain has time to process subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2) - success, children, error = subtensor.get_children( - alice_wallet.hotkey.ss58_address, + success, children, error = subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -289,29 +362,34 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w assert success is True assert children == [(1.0, bob_wallet.hotkey.ss58_address)] - parent_ = subtensor.get_parents(bob_wallet.hotkey.ss58_address, dave_subnet_netuid) + parent_ = subtensor.wallets.get_parents( + bob_wallet.hotkey.ss58_address, dave_subnet_netuid + ) assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] # pending queue is empty - pending, cooldown = subtensor.get_children_pending( - alice_wallet.hotkey.ss58_address, + pending, cooldown = subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) assert pending == [] + logging.console.info( + f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" + ) with pytest.raises(TxRateLimitExceeded): - set_children_block = subtensor.get_current_block() - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + set_children_block = subtensor.block + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, ) - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, @@ -319,26 +397,33 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + success, message = subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, ) - set_children_block = subtensor.get_current_block() + assert success, message - pending, cooldown = subtensor.get_children_pending( - alice_wallet.hotkey.ss58_address, + set_children_block = subtensor.block + + pending, cooldown = subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) assert pending == [] + logging.console.info( + f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" + ) subtensor.wait_for_block(cooldown) - success, children, error = subtensor.get_children( - alice_wallet.hotkey.ss58_address, + success, children, error = subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -349,8 +434,8 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_stake_threshold", call_params={ "min_stake": 1_000_000_000_000, @@ -358,9 +443,9 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ) with pytest.raises(NotEnoughStakeToSetChildkeys): - subtensor.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -372,3 +457,333 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w ) logging.console.success(f"✅ Test [green]test_children[/green] passed") + + +@pytest.mark.asyncio +async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): + """ + Async tests: + - Get default children (empty list) + - Call `root_set_pending_childkey_cooldown` extrinsic. + - Update children list + - Checking pending children + - Checking cooldown period + - Trigger rate limit + - Clear children list + """ + + logging.console.info("Testing [green]test_children_async[/green].") + + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + set_tempo = 10 # affect to non-fast-blocks mode + + # Set cooldown + ( + success, + message, + ) = await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( + wallet=alice_wallet, cooldown=ROOT_COOLDOWN + ) + assert success, f"Call `root_set_pending_childkey_cooldown` failed: {message}" + assert ( + message + == "Success with `root_set_pending_childkey_cooldown_extrinsic` response." + ) + + assert await async_subtensor.subnets.register_subnet(dave_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( + f"Subnet #{dave_subnet_netuid} does not exist." + ) + + assert ( + await async_wait_to_start_call(async_subtensor, dave_wallet, dave_subnet_netuid) + is True + ) + + # set the same tempo for both type of nodes (to avoid tests timeout) + if not await async_subtensor.chain.is_fast_blocks(): + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": dave_subnet_netuid, "tempo": set_tempo}, + ) + )[0] is True + + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tx_rate_limit", + call_params={"tx_rate_limit": 0}, + ) + )[0] is True + assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == set_tempo + assert await async_subtensor.chain.tx_rate_limit(dave_subnet_netuid) == 0 + + with pytest.raises(RegistrationNotPermittedOnRootSubnet): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=0, + children=[], + raise_error=True, + ) + + with pytest.raises(NonAssociatedColdKey): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=1, + children=[], + raise_error=True, + ) + + with pytest.raises(SubNetworkDoesNotExist): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=3, + children=[], + raise_error=True, + ) + + assert await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dave_subnet_netuid, + ) + logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") + + assert await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ) + logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") + + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + + assert error == "" + assert success is True + assert children == [] + + with pytest.raises(InvalidChild): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + alice_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) + + with pytest.raises(TooManyChildren): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 0.1, + bob_wallet.hotkey.ss58_address, + ) + for _ in range(10) + ], + raise_error=True, + ) + + with pytest.raises(ProportionOverflow): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ], + raise_error=True, + ) + + with pytest.raises(DuplicateChild): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 0.5, + bob_wallet.hotkey.ss58_address, + ), + ( + 0.5, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) + + success, message = await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert message == "Success with `set_children_extrinsic` response." + assert success is True + logging.console.info(f"[orange]success: {success}, message: {message}[/orange]") + + set_children_block = await async_subtensor.chain.get_current_block() + + # children not set yet (have to wait cool-down period) + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + block=set_children_block, + netuid=dave_subnet_netuid, + ) + + assert success is True + assert children == [] + assert error == "" + + # children are in pending state + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) + + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] + + # we use `*2` to ensure that the chain has time to process + await async_subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2) + + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + + assert error == "" + assert success is True + assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + + parent_ = await async_subtensor.wallets.get_parents( + bob_wallet.hotkey.ss58_address, dave_subnet_netuid + ) + + assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] + + # pending queue is empty + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) + + with pytest.raises(TxRateLimitExceeded): + set_children_block = await async_subtensor.chain.get_current_block() + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[], + raise_error=True, + ) + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[], + raise_error=True, + ) + + await async_subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + + success, message = await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[], + raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert message == "Success with `set_children_extrinsic` response." + assert success is True + logging.console.info(f"[orange]success: {success}, message: {message}[/orange]") + + set_children_block = await async_subtensor.chain.get_current_block() + + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) + + await async_subtensor.wait_for_block(cooldown) + + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + + assert error == "" + assert success is True + assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + + await async_subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_stake_threshold", + call_params={ + "min_stake": 1_000_000_000_000, + }, + ) + + with pytest.raises(NotEnoughStakeToSetChildkeys): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) + + logging.console.success(f"✅ Test [green]test_children_async[/green] passed") From d9c6e16d90c654034d4062e557447279892bd669 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 15:32:55 -0700 Subject: [PATCH 16/86] `actions/checkout@v4` should take all commits history --- .github/workflows/e2e-subtensor-tests.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index d56e9a726d..f2bbc542b8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -32,6 +32,8 @@ jobs: steps: - name: Check-out repository under $GITHUB_WORKSPACE uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 @@ -147,6 +149,8 @@ jobs: steps: - name: Check-out repository uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 From 42d593d253646aeea99f86fedc1f07e8044172cb Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 15:54:48 -0700 Subject: [PATCH 17/86] Checkout PR head instead of merge --- .github/workflows/e2e-subtensor-tests.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index f2bbc542b8..e88eb588f2 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -34,6 +34,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python uses: actions/setup-python@v5 @@ -151,6 +152,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 From 85eb48d822ffd39c00f0047d741bc6ba7f910bbc Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 17:51:12 -0700 Subject: [PATCH 18/86] add lost data --- .github/workflows/e2e-subtensor-tests.yaml | 6 ------ tests/e2e_tests/test_delegate.py | 2 +- tests/e2e_tests/test_stake_fee.py | 14 ++++++++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index e88eb588f2..d56e9a726d 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -32,9 +32,6 @@ jobs: steps: - name: Check-out repository under $GITHUB_WORKSPACE uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python uses: actions/setup-python@v5 @@ -150,9 +147,6 @@ jobs: steps: - name: Check-out repository uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index f681197e49..949b1324be 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -342,7 +342,7 @@ def test_nominator_min_required_stake( wallet=dave_wallet, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, - amount=Balance.from_tao(10_000), + amount=Balance.from_tao(1000), wait_for_inclusion=True, wait_for_finalization=True, ) diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index b20440c651..049ee3cbe7 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -19,7 +19,7 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): netuid = 2 root_netuid = 0 stake_amount = Balance.from_tao(100) # 100 TAO - min_stake_fee = Balance.from_tao(0.299076829) + min_stake_fee = Balance.from_tao(0.050354772) # Register subnet as Alice assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" @@ -32,9 +32,9 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): coldkey_ss58=alice_wallet.coldkeypub.ss58_address, hotkey_ss58=alice_wallet.hotkey.ss58_address, ) - assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object" + assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object." assert stake_fee_0 == min_stake_fee, ( - "Stake fee should be equal the minimum stake fee" + "Stake fee should be equal the minimum stake fee." ) # Test unstake fee @@ -44,9 +44,11 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): coldkey_ss58=alice_wallet.coldkeypub.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, ) - assert isinstance(unstake_fee_root, Balance), "Stake fee should be a Balance object" - assert unstake_fee_root == Balance.from_rao(299076829), ( - "Root unstake fee should be 0." + assert isinstance(unstake_fee_root, Balance), ( + "Stake fee should be a Balance object." + ) + assert unstake_fee_root == min_stake_fee, ( + "Root unstake fee should be equal the minimum stake fee." ) # Test various stake movement scenarios From 4031a22fb47f5289337b0e7355ab440823d7c660 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:01:57 -0700 Subject: [PATCH 19/86] improve `SubtensorApi` --- bittensor/core/subtensor_api/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py index 5ae0bf7134..dbf261c341 100644 --- a/bittensor/core/subtensor_api/__init__.py +++ b/bittensor/core/subtensor_api/__init__.py @@ -178,7 +178,8 @@ async def __aenter__(self): raise NotImplementedError( "Sync version of SubtensorApi cannot be used with async context manager." ) - return await self._subtensor.__aenter__() + await self._subtensor.__aenter__() + return self async def __aexit__(self, exc_type, exc_val, exc_tb): if not self.is_async: From 991a01a83b82e4960bcfaec2ce551b56659f8282 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:02:53 -0700 Subject: [PATCH 20/86] `legacy_methods=False` for all tests --- tests/e2e_tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index c9ed635971..2ca74c2063 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -266,13 +266,13 @@ def templates(): @pytest.fixture def subtensor(local_chain): - return SubtensorApi(network="ws://localhost:9944", legacy_methods=True) + return SubtensorApi(network="ws://localhost:9944", legacy_methods=False) @pytest.fixture def async_subtensor(local_chain): return SubtensorApi( - network="ws://localhost:9944", legacy_methods=True, async_subtensor=True + network="ws://localhost:9944", legacy_methods=False, async_subtensor=True ) From f99c372be7e3168d38ea677aa20d43462296d296 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:04:01 -0700 Subject: [PATCH 21/86] apply related changes to test utils --- tests/e2e_tests/utils/e2e_test_utils.py | 41 +++++++++++++------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index d41c80982d..9a1ba403bb 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -4,15 +4,16 @@ import subprocess import sys -from bittensor_wallet import Keypair +from bittensor_wallet import Keypair, Wallet -import bittensor +from bittensor.core.subtensor_api import SubtensorApi +from bittensor.utils.btlogging import logging template_path = os.getcwd() + "/neurons/" templates_repo = "templates repository" -def setup_wallet(uri: str) -> tuple[Keypair, bittensor.Wallet]: +def setup_wallet(uri: str) -> tuple[Keypair, Wallet]: """ Sets up a wallet using the provided URI. @@ -26,7 +27,7 @@ def setup_wallet(uri: str) -> tuple[Keypair, bittensor.Wallet]: """ keypair = Keypair.create_from_uri(uri) wallet_path = f"/tmp/btcli-e2e-wallet-{uri.strip('/')}" - wallet = bittensor.Wallet(path=wallet_path) + wallet = Wallet(path=wallet_path) wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=True) wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=True) wallet.set_hotkey(keypair=keypair, encrypt=False, overwrite=True) @@ -135,10 +136,10 @@ async def __aexit__(self, exc_type, exc_value, traceback): async def _reader(self): async for line in self.process.stdout: try: - bittensor.logging.console.info( + logging.console.info( f"[green]MINER LOG: {line.split(b'|')[-1].strip().decode()}[/blue]" ) - except BaseException: + except Exception: # skipp empty lines pass @@ -198,18 +199,18 @@ async def __aexit__(self, exc_type, exc_value, traceback): async def _reader(self): async for line in self.process.stdout: try: - bittensor.logging.console.info( + logging.console.info( f"[orange]VALIDATOR LOG: {line.split(b'|')[-1].strip().decode()}[/orange]" ) - except BaseException: + except Exception: # skipp empty lines pass if b"Starting validator loop." in line: - bittensor.logging.console.info("Validator started.") + logging.console.info("Validator started.") self.started.set() elif b"Successfully set weights and Finalized." in line: - bittensor.logging.console.info("Validator is setting weights.") + logging.console.info("Validator is setting weights.") self.set_weights.set() def __init__(self): @@ -229,15 +230,15 @@ def validator(self, wallet, netuid): def wait_to_start_call( - subtensor: "bittensor.SubtensorApi", - subnet_owner_wallet: "bittensor.Wallet", + subtensor: SubtensorApi, + subnet_owner_wallet: "Wallet", netuid: int, in_blocks: int = 10, ): """Waits for a certain number of blocks before making a start call.""" - if subtensor.is_fast_blocks() is False: + if subtensor.chain.is_fast_blocks() is False: in_blocks = 5 - bittensor.logging.console.info( + logging.console.info( f"Waiting for [blue]{in_blocks}[/blue] blocks before [red]start call[/red]. " f"Current block: [blue]{subtensor.block}[/blue]." ) @@ -249,7 +250,7 @@ def wait_to_start_call( # make sure we passed start_call limit subtensor.wait_for_block(subtensor.block + in_blocks + 1) - status, message = subtensor.start_call( + status, message = subtensor.extrinsics.start_call( wallet=subnet_owner_wallet, netuid=netuid, wait_for_inclusion=True, @@ -265,25 +266,25 @@ def wait_to_start_call( async def async_wait_to_start_call( - subtensor: "bittensor.AsyncSubtensor", - subnet_owner_wallet: "bittensor.Wallet", + subtensor: "SubtensorApi", + subnet_owner_wallet: "Wallet", netuid: int, in_blocks: int = 10, ): """Waits for a certain number of blocks before making a start call.""" - if await subtensor.is_fast_blocks() is False: + if await subtensor.subnets.is_fast_blocks() is False: in_blocks = 5 current_block = await subtensor.block - bittensor.logging.console.info( + logging.console.info( f"Waiting for [blue]{in_blocks}[/blue] blocks before [red]start call[/red]. " f"Current block: [blue]{current_block}[/blue]." ) # make sure we passed start_call limit await subtensor.wait_for_block(current_block + in_blocks + 1) - status, message = await subtensor.start_call( + status, message = await subtensor.extrinsics.start_call( wallet=subnet_owner_wallet, netuid=netuid, wait_for_inclusion=True, From 12d6540fc75844d2948b334d9040754f21eb8ca4 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:06:08 -0700 Subject: [PATCH 22/86] apply related changes to `chain_interactions.py` --- tests/e2e_tests/utils/chain_interactions.py | 41 ++++++++++----------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 252288d276..ac4afe8e45 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -14,13 +14,12 @@ # for typing purposes if TYPE_CHECKING: from bittensor import Wallet - from bittensor.core.async_subtensor import AsyncSubtensor - from bittensor.core.subtensor import Subtensor + from bittensor.core.subtensor_api import SubtensorApi from async_substrate_interface import ( - SubstrateInterface, - ExtrinsicReceipt, AsyncSubstrateInterface, AsyncExtrinsicReceipt, + SubstrateInterface, + ExtrinsicReceipt, ) @@ -101,7 +100,7 @@ def sudo_set_hyperparameter_values( return response.is_success -async def wait_epoch(subtensor: "Subtensor", netuid: int = 1, **kwargs): +async def wait_epoch(subtensor: "SubtensorApi", netuid: int = 1, **kwargs): """ Waits for the next epoch to start on a specific subnet. @@ -112,7 +111,7 @@ async def wait_epoch(subtensor: "Subtensor", netuid: int = 1, **kwargs): Raises: Exception: If the tempo cannot be determined from the chain. """ - q_tempo = [v for (k, v) in subtensor.query_map_subtensor("Tempo") if k == netuid] + q_tempo = [v for (k, v) in subtensor.queries.query_map_subtensor("Tempo") if k == netuid] if len(q_tempo) == 0: raise Exception("could not determine tempo") tempo = q_tempo[0].value @@ -121,7 +120,7 @@ async def wait_epoch(subtensor: "Subtensor", netuid: int = 1, **kwargs): async def async_wait_epoch( - async_subtensor: "AsyncSubtensor", netuid: int = 1, **kwargs + async_subtensor: "SubtensorApi", netuid: int = 1, **kwargs ): """ Waits for the next epoch to start on a specific subnet. @@ -135,7 +134,7 @@ async def async_wait_epoch( """ q_tempo = [ v - async for (k, v) in await async_subtensor.query_map_subtensor("Tempo") + async for (k, v) in await async_subtensor.queries.query_map_subtensor("Tempo") if k == netuid ] if len(q_tempo) == 0: @@ -161,7 +160,7 @@ def next_tempo(current_block: int, tempo: int) -> int: async def wait_interval( tempo: int, - subtensor: "Subtensor", + subtensor: "SubtensorApi", netuid: int = 1, reporting_interval: int = 1, sleep: float = 0.25, @@ -174,7 +173,7 @@ async def wait_interval( and the provided tempo, then enters a loop where it periodically checks the current block number until the next tempo interval starts. """ - current_block = subtensor.get_current_block() + current_block = subtensor.chain.get_current_block() next_tempo_block_start = current_block for _ in range(times): @@ -186,7 +185,7 @@ async def wait_interval( await asyncio.sleep( sleep, ) # Wait before checking the block number again - current_block = subtensor.get_current_block() + current_block = subtensor.chain.get_current_block() if last_reported is None or current_block - last_reported >= reporting_interval: last_reported = current_block print( @@ -199,7 +198,7 @@ async def wait_interval( async def async_wait_interval( tempo: int, - subtensor: "AsyncSubtensor", + subtensor: "SubtensorApi", netuid: int = 1, reporting_interval: int = 1, sleep: float = 0.25, @@ -212,7 +211,7 @@ async def async_wait_interval( and the provided tempo, then enters a loop where it periodically checks the current block number until the next tempo interval starts. """ - current_block = await subtensor.get_current_block() + current_block = await subtensor.chain.get_current_block() next_tempo_block_start = current_block for _ in range(times): @@ -224,7 +223,7 @@ async def async_wait_interval( await asyncio.sleep( sleep, ) # Wait before checking the block number again - current_block = await subtensor.get_current_block() + current_block = await subtensor.chain.get_current_block() if last_reported is None or current_block - last_reported >= reporting_interval: last_reported = current_block print( @@ -236,7 +235,7 @@ async def async_wait_interval( def execute_and_wait_for_next_nonce( - subtensor, wallet, sleep=0.25, timeout=60.0, max_retries=3 + subtensor: "SubtensorApi", wallet, sleep=0.25, timeout=60.0, max_retries=3 ): """Decorator that ensures the nonce has been consumed after a blockchain extrinsic call.""" @@ -389,7 +388,7 @@ def root_set_subtensor_hyperparameter_values( def set_identity( - subtensor, + subtensor: "SubtensorApi", wallet, name="", url="", @@ -420,7 +419,7 @@ def set_identity( async def async_set_identity( - subtensor: "AsyncSubtensor", + subtensor: "SubtensorApi", wallet: "Wallet", name="", url="", @@ -468,7 +467,7 @@ def propose(subtensor, wallet, proposal, duration): async def async_propose( - subtensor: "AsyncSubtensor", + subtensor: "SubtensorApi", wallet: "Wallet", proposal, duration, @@ -490,8 +489,8 @@ async def async_propose( def vote( - subtensor, - wallet, + subtensor: "SubtensorApi", + wallet: "Wallet", hotkey, proposal, index, @@ -515,7 +514,7 @@ def vote( async def async_vote( - subtensor: "AsyncSubtensor", + subtensor: "SubtensorApi", wallet: "Wallet", hotkey, proposal, From 6a0a5abcfff6d385f54a55dc9cf0e6d3aefc9fcc Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:08:08 -0700 Subject: [PATCH 23/86] apply SubtensorApi changes to updated tess --- tests/e2e_tests/test_commit_weights.py | 60 +++++++++---------- tests/e2e_tests/test_commitment.py | 40 ++++++------- .../test_cross_subtensor_compatibility.py | 11 ++-- tests/e2e_tests/test_delegate.py | 18 +++--- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 8e524f890a..be544e8493 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -41,7 +41,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa ) # Verify subnet 2 created successfully - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( @@ -114,7 +114,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa assert success is True - weight_commits = subtensor.query_module( + weight_commits = subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", params=[netuid, alice_wallet.hotkey.ss58_address], @@ -146,7 +146,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa assert success is True # Query the Weights storage map - revealed_weights = subtensor.query_module( + revealed_weights = subtensor.queries.query_module( module="SubtensorModule", name="Weights", params=[netuid, 0], # netuid and uid @@ -186,7 +186,7 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal ) # Verify subnet 2 created successfully - assert await async_subtensor.subnet_exists(netuid), ( + assert await async_subtensor.subnets.subnet_exists(netuid), ( "Subnet wasn't created successfully" ) @@ -259,7 +259,7 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal assert success is True - weight_commits = await async_subtensor.query_module( + weight_commits = await async_subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", params=[netuid, alice_wallet.hotkey.ss58_address], @@ -291,7 +291,7 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal assert success is True # Query the Weights storage map - revealed_weights = await async_subtensor.query_module( + revealed_weights = await async_subtensor.queries.query_module( module="SubtensorModule", name="Weights", params=[netuid, 0], # netuid and uid @@ -338,17 +338,17 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall """ logging.console.info("Testing test_commit_and_reveal_weights") - subnet_tempo = 50 if subtensor.is_fast_blocks() else 10 - netuid = subtensor.get_total_subnets() # 2 + subnet_tempo = 50 if subtensor.chain.is_fast_blocks() else 10 + netuid = subtensor.subnets.get_total_subnets() # 2 # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos subtensor.wait_for_block(subtensor.block + (subnet_tempo * 2) + 1) # Register root as Alice - assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" + assert subtensor.subnets.register_subnet(alice_wallet), "Unable to register the subnet" # Verify subnet 1 created successfully - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" # weights sensitive to epoch changes assert sudo_set_admin_utils( @@ -370,13 +370,13 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall netuid, ), "Unable to enable commit reveal on the subnet" - assert subtensor.commit_reveal_enabled(netuid), "Failed to enable commit/reveal" + assert subtensor.commitments.commit_reveal_enabled(netuid), "Failed to enable commit/reveal" assert ( - subtensor.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period == 1 + subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period == 1 ), "Failed to set commit/reveal periods" - assert subtensor.weights_rate_limit(netuid=netuid) > 0, ( + assert subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( "Weights rate limit is below 0" ) @@ -391,9 +391,9 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall assert error is None and status is True, f"Failed to set rate limit: {error}" assert ( - subtensor.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0 + subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0 ), "Failed to set weights_rate_limit" - assert subtensor.weights_rate_limit(netuid=netuid) == 0 + assert subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 # wait while weights_rate_limit changes applied. subtensor.wait_for_block(subnet_tempo + 1) @@ -407,7 +407,7 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall @retry.retry(exceptions=Exception, tries=3, delay=1) @execute_and_wait_for_next_nonce(subtensor=subtensor, wallet=alice_wallet) def send_commit(salt_, weight_uids_, weight_vals_): - success, message = subtensor.commit_weights( + success, message = subtensor.extrinsics.commit_weights( wallet=alice_wallet, netuid=netuid, salt=salt_, @@ -426,7 +426,7 @@ def send_commit(salt_, weight_uids_, weight_vals_): send_commit(salt, weight_uids, weight_vals) # let's wait for 3 (12 fast blocks) seconds between transactions, next block for non-fast-blocks - waiting_block = (subtensor.block + 12) if subtensor.is_fast_blocks() else None + waiting_block = (subtensor.block + 12) if subtensor.chain.is_fast_blocks() else None subtensor.wait_for_block(waiting_block) logging.console.info( @@ -436,14 +436,14 @@ def send_commit(salt_, weight_uids_, weight_vals_): # Wait a few blocks waiting_block = ( - (subtensor.block + subtensor.tempo(netuid) * 2) - if subtensor.is_fast_blocks() + (subtensor.block + subtensor.subnets.tempo(netuid) * 2) + if subtensor.chain.is_fast_blocks() else None ) subtensor.wait_for_block(waiting_block) # Query the WeightCommits storage map for all three salts - weight_commits = subtensor.query_module( + weight_commits = subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", params=[netuid, alice_wallet.hotkey.ss58_address], @@ -477,8 +477,8 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle """ logging.console.info("Testing test_commit_and_reveal_weights") - subnet_tempo = 50 if await async_subtensor.is_fast_blocks() else 10 - netuid = await async_subtensor.get_total_subnets() # 2 + subnet_tempo = 50 if await async_subtensor.chain.is_fast_blocks() else 10 + netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos await async_subtensor.wait_for_block( @@ -486,12 +486,12 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle ) # Register root as Alice - assert await async_subtensor.register_subnet(alice_wallet), ( + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" ) # Verify subnet 1 created successfully - assert await async_subtensor.subnet_exists(netuid), ( + assert await async_subtensor.subnets.subnet_exists(netuid), ( "Subnet wasn't created successfully" ) @@ -515,15 +515,15 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle netuid=netuid, ), "Unable to enable commit reveal on the subnet" - assert await async_subtensor.commit_reveal_enabled(netuid), ( + assert await async_subtensor.commitments.commit_reveal_enabled(netuid), ( "Failed to enable commit/reveal" ) assert ( - await async_subtensor.get_subnet_hyperparameters(netuid=netuid) + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) ).commit_reveal_period == 1, "Failed to set commit/reveal periods" - assert await async_subtensor.weights_rate_limit(netuid=netuid) > 0, ( + assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( "Weights rate limit is below 0" ) @@ -538,9 +538,9 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle assert error is None and status is True, f"Failed to set rate limit: {error}" assert ( - await async_subtensor.get_subnet_hyperparameters(netuid=netuid) + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) ).weights_rate_limit == 0, "Failed to set weights_rate_limit" - assert await async_subtensor.weights_rate_limit(netuid=netuid) == 0 + assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 # wait while weights_rate_limit changes applied. await async_subtensor.wait_for_block(subnet_tempo + 1) @@ -630,7 +630,7 @@ async def send_commit_(): await async_subtensor.wait_for_block(waiting_block) # Query the WeightCommits storage map for all three salts - weight_commits = await async_subtensor.query_module( + weight_commits = await async_subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", params=[netuid, alice_wallet.hotkey.ss58_address], diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index 338b39d851..860601f075 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -16,38 +16,38 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): dave_subnet_netuid = 2 - assert subtensor.register_subnet(dave_wallet, True, True) - assert subtensor.subnet_exists(dave_subnet_netuid), ( + assert subtensor.subnets.register_subnet(dave_wallet, True, True) + assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( "Subnet wasn't created successfully" ) assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): - subtensor.set_commitment( + subtensor.commitments.set_commitment( alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", ) - assert subtensor.burned_register( + assert subtensor.subnets.burned_register( alice_wallet, netuid=dave_subnet_netuid, ) - uid = subtensor.get_uid_for_hotkey_on_subnet( + uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) assert uid is not None - assert "" == subtensor.get_commitment( + assert "" == subtensor.commitments.get_commitment( netuid=dave_subnet_netuid, uid=uid, ) - assert subtensor.set_commitment( + assert subtensor.commitments.set_commitment( wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", @@ -70,19 +70,19 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): SubstrateRequestException, match="SpaceLimitExceeded", ): - subtensor.set_commitment( + subtensor.commitments.set_commitment( wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!1", ) - assert "Hello World!" == subtensor.get_commitment( + assert "Hello World!" == subtensor.commitments.get_commitment( netuid=dave_subnet_netuid, uid=uid, ) assert ( - subtensor.get_all_commitments(netuid=dave_subnet_netuid)[ + subtensor.commitments.get_all_commitments(netuid=dave_subnet_netuid)[ alice_wallet.hotkey.ss58_address ] == "Hello World!" @@ -92,8 +92,8 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): @pytest.mark.asyncio async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): dave_subnet_netuid = 2 - assert await async_subtensor.register_subnet(dave_wallet) - assert await async_subtensor.subnet_exists(dave_subnet_netuid), ( + assert await async_subtensor.subnets.register_subnet(dave_wallet) + assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -103,30 +103,30 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): async with async_subtensor as sub: with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): - await sub.set_commitment( + await sub.commitments.set_commitment( alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", ) - assert await sub.burned_register( + assert await sub.subnets.burned_register( alice_wallet, netuid=dave_subnet_netuid, ) - uid = await sub.get_uid_for_hotkey_on_subnet( + uid = await sub.subnets.get_uid_for_hotkey_on_subnet( alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) assert uid is not None - assert "" == await sub.get_commitment( + assert "" == await sub.commitments.get_commitment( netuid=dave_subnet_netuid, uid=uid, ) - assert await sub.set_commitment( + assert await sub.commitments.set_commitment( alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", @@ -149,17 +149,17 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): SubstrateRequestException, match="SpaceLimitExceeded", ): - await sub.set_commitment( + await sub.commitments.set_commitment( alice_wallet, netuid=dave_subnet_netuid, data="Hello World!1", ) - assert "Hello World!" == await sub.get_commitment( + assert "Hello World!" == await sub.commitments.get_commitment( netuid=dave_subnet_netuid, uid=uid, ) - assert (await sub.get_all_commitments(netuid=dave_subnet_netuid))[ + assert (await sub.commitments.get_all_commitments(netuid=dave_subnet_netuid))[ alice_wallet.hotkey.ss58_address ] == "Hello World!" diff --git a/tests/e2e_tests/test_cross_subtensor_compatibility.py b/tests/e2e_tests/test_cross_subtensor_compatibility.py index e26769a1df..3198797643 100644 --- a/tests/e2e_tests/test_cross_subtensor_compatibility.py +++ b/tests/e2e_tests/test_cross_subtensor_compatibility.py @@ -1,18 +1,19 @@ from datetime import datetime + import pytest @pytest.mark.asyncio async def test_get_timestamp(subtensor, async_subtensor, local_chain): with subtensor: - block_number = subtensor.get_current_block() + block_number = subtensor.chain.get_current_block() assert isinstance( - subtensor.get_timestamp(), datetime + subtensor.chain.get_timestamp(), datetime ) # verify it works with no block number specified - sync_result = subtensor.get_timestamp( + sync_result = subtensor.chain.get_timestamp( block=block_number ) # verify it works with block number specified async with async_subtensor: - assert isinstance(await async_subtensor.get_timestamp(), datetime) - async_result = await async_subtensor.get_timestamp(block=block_number) + assert isinstance(await async_subtensor.chain.get_timestamp(), datetime) + async_result = await async_subtensor.chain.get_timestamp(block=block_number) assert sync_result == async_result diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 1b757211aa..a63bfaab39 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -37,7 +37,7 @@ def test_identity(subtensor, alice_wallet, bob_wallet): - Check Delegate's default identity - Update Delegate's identity """ - logging.console.info("Testing [green]test_identity_async[/green].") + logging.console.info("Testing [green]test_identity[/green].") identity = subtensor.neurons.query_identity(alice_wallet.coldkeypub.ss58_address) assert identity is None @@ -89,7 +89,7 @@ def test_identity(subtensor, alice_wallet, bob_wallet): name="Alice", url="https://www.example.com", ) - logging.console.success("Test [green]test_identity_async[/green] passed.") + logging.console.success("Test [green]test_identity[/green] passed.") @pytest.mark.asyncio @@ -99,7 +99,7 @@ async def test_identity_async(async_subtensor, alice_wallet, bob_wallet): - Check Delegate's default identity - Update Delegate's identity """ - logging.console.info("Testing [green]test_identity_async_async[/green].") + logging.console.info("Testing [green]test_identity_async[/green].") identity = await async_subtensor.neurons.query_identity( alice_wallet.coldkeypub.ss58_address @@ -660,7 +660,7 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ """ logging.console.info("Testing [green]test_delegates_async[/green].") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 assert subtensor.subnets.register_subnet( @@ -749,7 +749,7 @@ async def test_nominator_min_required_stake_async( "Testing [green]test_nominator_min_required_stake_async[/green]." ) - alice_subnet_netuid = await async_subtensor.get_total_subnets() # 2 + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 assert await async_subtensor.subnets.register_subnet( @@ -839,7 +839,7 @@ def test_get_vote_data(subtensor, alice_wallet): "Can not register Alice in root SN." ) - proposals = subtensor.query_map( + proposals = subtensor.queries.query_map( "Triumvirate", "ProposalOf", params=[], @@ -865,7 +865,7 @@ def test_get_vote_data(subtensor, alice_wallet): assert error == "" assert success is True - proposals = subtensor.query_map( + proposals = subtensor.queries.query_map( module="Triumvirate", name="ProposalOf", params=[], @@ -946,7 +946,7 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): "Can not register Alice in root SN." ) - proposals = await async_subtensor.query_map( + proposals = await async_subtensor.queries.query_map( "Triumvirate", "ProposalOf", params=[], @@ -972,7 +972,7 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): assert error == "" assert success is True - proposals = await async_subtensor.query_map( + proposals = await async_subtensor.queries.query_map( module="Triumvirate", name="ProposalOf", params=[], From 3caaf29e9d9924d1a7bccffa9be87a1512e1e287 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:27:56 -0700 Subject: [PATCH 24/86] oops --- tests/e2e_tests/utils/e2e_test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 9a1ba403bb..f302662281 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -272,7 +272,7 @@ async def async_wait_to_start_call( in_blocks: int = 10, ): """Waits for a certain number of blocks before making a start call.""" - if await subtensor.subnets.is_fast_blocks() is False: + if await subtensor.chain.is_fast_blocks() is False: in_blocks = 5 current_block = await subtensor.block From eb4800d58da83c213e712404affa54525a0d2661 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:38:01 -0700 Subject: [PATCH 25/86] async test for `tests/e2e_tests/test_incentive.py` --- tests/e2e_tests/test_incentive.py | 209 +++++++++++++++++++++++++++--- 1 file changed, 192 insertions(+), 17 deletions(-) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 8e834eb7f7..ff5305099c 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -4,16 +4,18 @@ from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_admin_utils, + async_wait_epoch, sudo_set_admin_utils, wait_epoch, ) -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import async_wait_to_start_call, wait_to_start_call DURATION_OF_START_CALL = 10 @pytest.mark.asyncio -async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wallet): +async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): """ Test the incentive mechanism and interaction of miners/validators @@ -26,21 +28,21 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa AssertionError: If any of the checks or verifications fail """ - print("Testing test_incentive") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + logging.console.info("Testing [blue]test_incentive[/blue]") + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register root as Alice - the subnet owner and validator - assert subtensor.register_subnet(alice_wallet, True, True), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet, True, True), "Subnet wasn't created" # Verify subnet created successfully - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) # Disable commit_reveal on the subnet to check proper behavior status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_commit_reveal_weights_enabled", call_params={ "netuid": alice_subnet_netuid, @@ -52,12 +54,12 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) # Register Bob as a neuron on the subnet - assert subtensor.burned_register(bob_wallet, alice_subnet_netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( "Unable to register Bob as a neuron" ) # Assert two neurons are in network - assert len(subtensor.neurons(netuid=alice_subnet_netuid)) == 2, ( + assert len(subtensor.neurons.neurons(netuid=alice_subnet_netuid)) == 2, ( "Alice & Bob not registered in the subnet" ) @@ -65,7 +67,7 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa await wait_epoch(subtensor, alice_subnet_netuid) # Get current miner/validator stats - alice_neuron = subtensor.neurons(netuid=alice_subnet_netuid)[0] + alice_neuron = subtensor.neurons.neurons(netuid=alice_subnet_netuid)[0] assert alice_neuron.validator_permit is True assert alice_neuron.dividends == 0 @@ -74,7 +76,7 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa assert alice_neuron.consensus == 0 assert alice_neuron.rank == 0 - bob_neuron = subtensor.neurons(netuid=alice_subnet_netuid)[1] + bob_neuron = subtensor.neurons.neurons(netuid=alice_subnet_netuid)[1] assert bob_neuron.incentive == 0 assert bob_neuron.consensus == 0 @@ -82,10 +84,10 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa assert bob_neuron.trust == 0 # update weights_set_rate_limit for fast-blocks - tempo = subtensor.tempo(alice_subnet_netuid) + tempo = subtensor.subnets.tempo(alice_subnet_netuid) status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={ "netuid": alice_subnet_netuid, @@ -137,7 +139,7 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa while True: try: - neurons = subtensor.neurons(netuid=alice_subnet_netuid) + neurons = subtensor.neurons.neurons(netuid=alice_subnet_netuid) logging.info(f"neurons: {neurons}") # Get current emissions and validate that Alice has gotten tao @@ -158,7 +160,7 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa assert bob_neuron.rank > 0.5 assert bob_neuron.trust == 1 - bonds = subtensor.bonds(alice_subnet_netuid) + bonds = subtensor.subnets.bonds(alice_subnet_netuid) assert bonds == [ ( @@ -179,3 +181,176 @@ async def test_incentive(local_chain, subtensor, templates, alice_wallet, bob_wa except Exception: subtensor.wait_for_block(subtensor.block) continue + + logging.console.success("Test [green]test_incentive[/green] passed.") + + +@pytest.mark.asyncio +async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wallet): + """ + Test the incentive mechanism and interaction of miners/validators + + Steps: + 1. Register a subnet as Alice and register Bob + 2. Run Alice as validator & Bob as miner. Wait Epoch + 3. Verify miner has correct: trust, rank, consensus, incentive + 4. Verify validator has correct: validator_permit, validator_trust, dividends, stake + Raises: + AssertionError: If any of the checks or verifications fail + """ + + logging.console.info("Testing [blue]test_incentive[/blue]") + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Register root as Alice - the subnet owner and validator + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True), "Subnet wasn't created" + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + # Disable commit_reveal on the subnet to check proper behavior + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + call_params={ + "netuid": alice_subnet_netuid, + "enabled": False, + }, + ) + assert status is True, error + + assert await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid) + + # Register Bob as a neuron on the subnet + assert await async_subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( + "Unable to register Bob as a neuron" + ) + + # Assert two neurons are in network + assert len(await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid)) == 2, ( + "Alice & Bob not registered in the subnet" + ) + + # Wait for the first epoch to pass + await async_wait_epoch(async_subtensor, alice_subnet_netuid) + + # Get current miner/validator stats + alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[0] + + assert alice_neuron.validator_permit is True + assert alice_neuron.dividends == 0 + assert alice_neuron.validator_trust == 0 + assert alice_neuron.incentive == 0 + assert alice_neuron.consensus == 0 + assert alice_neuron.rank == 0 + + bob_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[1] + + assert bob_neuron.incentive == 0 + assert bob_neuron.consensus == 0 + assert bob_neuron.rank == 0 + assert bob_neuron.trust == 0 + + # update weights_set_rate_limit for fast-blocks + tempo = await async_subtensor.subnets.tempo(alice_subnet_netuid) + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={ + "netuid": alice_subnet_netuid, + "weights_set_rate_limit": tempo, + }, + ) + + assert error is None + assert status is True + + # max attempts to run miner and validator + max_attempt = 3 + while True: + try: + async with templates.miner(bob_wallet, alice_subnet_netuid) as miner: + await asyncio.wait_for(miner.started.wait(), 60) + + async with templates.validator( + alice_wallet, alice_subnet_netuid + ) as validator: + # wait for the Validator to process and set_weights + await asyncio.wait_for(validator.set_weights.wait(), 60) + break + except asyncio.TimeoutError: + if max_attempt > 0: + max_attempt -= 1 + continue + raise + + # wait one tempo (fast block) + next_epoch_start_block = await async_subtensor.subnets.get_next_epoch_start_block( + alice_subnet_netuid + ) + await async_subtensor.wait_for_block(next_epoch_start_block + tempo + 1) + + validators = (await async_subtensor.metagraphs.get_metagraph_info( + alice_subnet_netuid, field_indices=[72] + )).validators + + alice_uid = await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid + ) + assert validators[alice_uid] == 1 + + bob_uid = await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid + ) + assert validators[bob_uid] == 0 + + while True: + try: + neurons = await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid) + logging.info(f"neurons: {neurons}") + + # Get current emissions and validate that Alice has gotten tao + alice_neuron = neurons[0] + + assert alice_neuron.validator_permit is True + assert alice_neuron.dividends == 1.0 + assert alice_neuron.stake.tao > 0 + assert alice_neuron.validator_trust > 0.99 + assert alice_neuron.incentive < 0.5 + assert alice_neuron.consensus < 0.5 + assert alice_neuron.rank < 0.5 + + bob_neuron = neurons[1] + + assert bob_neuron.incentive > 0.5 + assert bob_neuron.consensus > 0.5 + assert bob_neuron.rank > 0.5 + assert bob_neuron.trust == 1 + + bonds = await async_subtensor.subnets.bonds(alice_subnet_netuid) + + assert bonds == [ + ( + 0, + [ + (0, 65535), + (1, 65535), + ], + ), + ( + 1, + [], + ), + ] + + print("✅ Passed test_incentive") + break + except Exception: + await async_subtensor.wait_for_block(await async_subtensor.block) + continue + + logging.console.success("Test [green]test_incentive[/green] passed.") From b84cd2c4914e7af0911df05a039a0884cb9b010c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:38:18 -0700 Subject: [PATCH 26/86] ruff --- tests/e2e_tests/test_commit_weights.py | 18 ++++++--- tests/e2e_tests/test_incentive.py | 41 ++++++++++++++------- tests/e2e_tests/utils/chain_interactions.py | 8 ++-- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index be544e8493..8266f19d00 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -345,7 +345,9 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall subtensor.wait_for_block(subtensor.block + (subnet_tempo * 2) + 1) # Register root as Alice - assert subtensor.subnets.register_subnet(alice_wallet), "Unable to register the subnet" + assert subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) # Verify subnet 1 created successfully assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" @@ -370,10 +372,13 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall netuid, ), "Unable to enable commit reveal on the subnet" - assert subtensor.commitments.commit_reveal_enabled(netuid), "Failed to enable commit/reveal" + assert subtensor.commitments.commit_reveal_enabled(netuid), ( + "Failed to enable commit/reveal" + ) assert ( - subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period == 1 + subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period + == 1 ), "Failed to set commit/reveal periods" assert subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( @@ -391,7 +396,8 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall assert error is None and status is True, f"Failed to set rate limit: {error}" assert ( - subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0 + subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit + == 0 ), "Failed to set weights_rate_limit" assert subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 @@ -426,7 +432,9 @@ def send_commit(salt_, weight_uids_, weight_vals_): send_commit(salt, weight_uids, weight_vals) # let's wait for 3 (12 fast blocks) seconds between transactions, next block for non-fast-blocks - waiting_block = (subtensor.block + 12) if subtensor.chain.is_fast_blocks() else None + waiting_block = ( + (subtensor.block + 12) if subtensor.chain.is_fast_blocks() else None + ) subtensor.wait_for_block(waiting_block) logging.console.info( diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index ff5305099c..70cda9b48b 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -9,7 +9,10 @@ sudo_set_admin_utils, wait_epoch, ) -from tests.e2e_tests.utils.e2e_test_utils import async_wait_to_start_call, wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) DURATION_OF_START_CALL = 10 @@ -32,7 +35,9 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register root as Alice - the subnet owner and validator - assert subtensor.subnets.register_subnet(alice_wallet, True, True), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( + "Subnet wasn't created" + ) # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -203,7 +208,9 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Register root as Alice - the subnet owner and validator - assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True), "Subnet wasn't created" + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True), ( + "Subnet wasn't created" + ) # Verify subnet created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -222,23 +229,27 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal ) assert status is True, error - assert await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid) + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) # Register Bob as a neuron on the subnet - assert await async_subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( - "Unable to register Bob as a neuron" - ) + assert await async_subtensor.subnets.burned_register( + bob_wallet, alice_subnet_netuid + ), "Unable to register Bob as a neuron" # Assert two neurons are in network - assert len(await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid)) == 2, ( - "Alice & Bob not registered in the subnet" - ) + assert ( + len(await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid)) == 2 + ), "Alice & Bob not registered in the subnet" # Wait for the first epoch to pass await async_wait_epoch(async_subtensor, alice_subnet_netuid) # Get current miner/validator stats - alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[0] + alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[ + 0 + ] assert alice_neuron.validator_permit is True assert alice_neuron.dividends == 0 @@ -294,9 +305,11 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal ) await async_subtensor.wait_for_block(next_epoch_start_block + tempo + 1) - validators = (await async_subtensor.metagraphs.get_metagraph_info( - alice_subnet_netuid, field_indices=[72] - )).validators + validators = ( + await async_subtensor.metagraphs.get_metagraph_info( + alice_subnet_netuid, field_indices=[72] + ) + ).validators alice_uid = await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index ac4afe8e45..7137ad2497 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -111,7 +111,9 @@ async def wait_epoch(subtensor: "SubtensorApi", netuid: int = 1, **kwargs): Raises: Exception: If the tempo cannot be determined from the chain. """ - q_tempo = [v for (k, v) in subtensor.queries.query_map_subtensor("Tempo") if k == netuid] + q_tempo = [ + v for (k, v) in subtensor.queries.query_map_subtensor("Tempo") if k == netuid + ] if len(q_tempo) == 0: raise Exception("could not determine tempo") tempo = q_tempo[0].value @@ -119,9 +121,7 @@ async def wait_epoch(subtensor: "SubtensorApi", netuid: int = 1, **kwargs): await wait_interval(tempo, subtensor, netuid, **kwargs) -async def async_wait_epoch( - async_subtensor: "SubtensorApi", netuid: int = 1, **kwargs -): +async def async_wait_epoch(async_subtensor: "SubtensorApi", netuid: int = 1, **kwargs): """ Waits for the next epoch to start on a specific subnet. From 09ff34f342f77e8997aada146a9e4f9791ee850b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:10:23 -0700 Subject: [PATCH 27/86] async e2e test for `tests/e2e_tests/test_incentive.py` --- tests/e2e_tests/test_incentive.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 70cda9b48b..47b3cf2825 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -247,9 +247,9 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal await async_wait_epoch(async_subtensor, alice_subnet_netuid) # Get current miner/validator stats - alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[ - 0 - ] + alice_neuron = ( + await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid) + )[0] assert alice_neuron.validator_permit is True assert alice_neuron.dividends == 0 From 1082f6dd99395b436ee0f3184562a73a3a0454f3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:34:29 -0700 Subject: [PATCH 28/86] improve `chain_interactions.py` --- tests/e2e_tests/utils/chain_interactions.py | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 7137ad2497..c3ced7527f 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -100,6 +100,33 @@ def sudo_set_hyperparameter_values( return response.is_success +async def async_sudo_set_hyperparameter_values( + substrate: "AsyncSubstrateInterface", + wallet: "Wallet", + call_function: str, + call_params: dict, + return_error_message: bool = False, +) -> Union[bool, tuple[bool, Optional[str]]]: + """Sets liquid alpha values using AdminUtils. Mimics setting hyperparams.""" + call = await substrate.compose_call( + call_module="AdminUtils", + call_function=call_function, + call_params=call_params, + ) + extrinsic = await substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey) + response = await substrate.submit_extrinsic( + extrinsic=extrinsic, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + if return_error_message: + return await response.is_success, await response.error_message + + return await response.is_success + + + async def wait_epoch(subtensor: "SubtensorApi", netuid: int = 1, **kwargs): """ Waits for the next epoch to start on a specific subnet. From e012d7bf4931d06aee46a0742a13481f3f436439 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:37:20 -0700 Subject: [PATCH 29/86] async test for `tests/e2e_tests/test_liquid_alpha.py` --- tests/e2e_tests/test_liquid_alpha.py | 259 +++++++++++++++++++++++---- 1 file changed, 226 insertions(+), 33 deletions(-) diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index d907f53598..b92ab74fe2 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -1,9 +1,14 @@ +import pytest + from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_hyperparameter_bool, + async_sudo_set_hyperparameter_values, sudo_set_hyperparameter_bool, sudo_set_hyperparameter_values, ) +from tests.e2e_tests.utils.e2e_test_utils import async_wait_to_start_call, wait_to_start_call def liquid_alpha_call_params(netuid: int, alpha_values: str): @@ -15,7 +20,7 @@ def liquid_alpha_call_params(netuid: int, alpha_values: str): } -def test_liquid_alpha(local_chain, subtensor, alice_wallet): +def test_liquid_alpha(subtensor, alice_wallet): """ Test the liquid alpha mechanism. By June 17 2025 the limits are `0.025 <= alpha_low <= alpha_high <= 1` @@ -28,31 +33,36 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing [blue]test_liquid_alpha[/blue]") + u16_max = 65535 netuid = 2 - logging.console.info("Testing test_liquid_alpha_enabled") # Register root as Alice - assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" + assert subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) # Verify subnet created successfully - assert subtensor.subnet_exists(netuid) + assert subtensor.subnets.subnet_exists(netuid) + + assert wait_to_start_call(subtensor, alice_wallet, netuid) # Register a neuron (Alice) to the subnet - assert subtensor.burned_register(alice_wallet, netuid), ( + assert subtensor.subnets.burned_register(alice_wallet, netuid), ( "Unable to register Alice as a neuron" ) # Stake to become to top neuron after the first epoch - subtensor.add_stake( - alice_wallet, + assert subtensor.staking.add_stake( + wallet=alice_wallet, netuid=netuid, amount=Balance.from_tao(10_000), - ) + ), "Unable to stake to Alice neuron" # Assert liquid alpha is disabled assert ( - subtensor.get_subnet_hyperparameters(netuid=netuid).liquid_alpha_enabled + subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).liquid_alpha_enabled is False ), "Liquid alpha is enabled by default" @@ -60,8 +70,8 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): alpha_values = "6553, 53083" call_params = liquid_alpha_call_params(netuid, alpha_values) result, error_message = sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, return_error_message=True, @@ -71,10 +81,14 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): # Enabled liquid alpha on the subnet assert sudo_set_hyperparameter_bool( - local_chain, alice_wallet, "sudo_set_liquid_alpha_enabled", True, netuid + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_liquid_alpha_enabled", + value=True, + netuid=netuid, ), "Unable to enable liquid alpha" - assert subtensor.get_subnet_hyperparameters( + assert subtensor.subnets.get_subnet_hyperparameters( netuid, ).liquid_alpha_enabled, "Failed to enable liquid alpha" @@ -82,15 +96,15 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): alpha_values = "26001, 54099" call_params = liquid_alpha_call_params(netuid, alpha_values) assert sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, ), "Unable to set alpha_values" - assert subtensor.get_subnet_hyperparameters(netuid).alpha_high == 54099, ( + assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_high == 54099, ( "Failed to set alpha high" ) - assert subtensor.get_subnet_hyperparameters(netuid).alpha_low == 26001, ( + assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_low == 26001, ( "Failed to set alpha low" ) @@ -101,8 +115,8 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): call_params = liquid_alpha_call_params(netuid, f"6553, {alpha_high_too_low}") result, error_message = sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, return_error_message=True, @@ -116,8 +130,8 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): call_params = liquid_alpha_call_params(netuid, f"6553, {alpha_high_too_high}") try: sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, return_error_message=True, @@ -131,8 +145,8 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): alpha_low_too_low = 0 call_params = liquid_alpha_call_params(netuid, f"{alpha_low_too_low}, 53083") result, error_message = sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, return_error_message=True, @@ -144,8 +158,8 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): alpha_low_too_high = 53084 call_params = liquid_alpha_call_params(netuid, f"{alpha_low_too_high}, 53083") result, error_message = sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, return_error_message=True, @@ -157,25 +171,204 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): alpha_values = "6553, 53083" call_params = liquid_alpha_call_params(netuid, alpha_values) assert sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, ), "Unable to set liquid alpha values" - assert subtensor.get_subnet_hyperparameters(netuid).alpha_high == 53083, ( + assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_high == 53083, ( "Failed to set alpha high" ) - assert subtensor.get_subnet_hyperparameters(netuid).alpha_low == 6553, ( + assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_low == 6553, ( "Failed to set alpha low" ) # Disable Liquid Alpha assert sudo_set_hyperparameter_bool( - local_chain, alice_wallet, "sudo_set_liquid_alpha_enabled", False, netuid + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_liquid_alpha_enabled", + value=False, + netuid=netuid, ), "Unable to disable liquid alpha" - assert subtensor.get_subnet_hyperparameters(netuid).liquid_alpha_enabled is False, ( - "Failed to disable liquid alpha" + assert ( + subtensor.subnets.get_subnet_hyperparameters(netuid).liquid_alpha_enabled + is False + ), "Failed to disable liquid alpha" + logging.console.info("✅ Passed [blue]test_liquid_alpha[/blue]") + + +@pytest.mark.asyncio +async def test_liquid_alpha_async(async_subtensor, alice_wallet): + """ + Async test the liquid alpha mechanism. By June 17 2025 the limits are `0.025 <= alpha_low <= alpha_high <= 1` + + Steps: + 1. Register a subnet through Alice + 2. Register Alice's neuron and add stake + 3. Verify we can't set alpha values without enabling liquid_alpha + 4. Test setting alpha values after enabling liquid_alpha + 5. Verify failures when setting incorrect values (upper and lower bounds) + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing [blue]test_liquid_alpha[/blue]") + + u16_max = 65535 + netuid = 2 + + # Register root as Alice + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(netuid) + + assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) + + # Register a neuron (Alice) to the subnet + assert await async_subtensor.subnets.burned_register(alice_wallet, netuid), ( + "Unable to register Alice as a neuron" + ) + + # Stake to become to top neuron after the first epoch + assert await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=netuid, + amount=Balance.from_tao(10_000), + ), "Unable to stake to Alice neuron" + + # Assert liquid alpha is disabled + assert (await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid)).liquid_alpha_enabled is False, "Liquid alpha is enabled by default" + + # Attempt to set alpha high/low while disabled (should fail) + alpha_values = "6553, 53083" + call_params = liquid_alpha_call_params(netuid, alpha_values) + result, error_message = await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_alpha_values", + call_params=call_params, + return_error_message=True, ) - logging.console.info("✅ Passed test_liquid_alpha") + assert result is False, "Alpha values set while being disabled" + assert error_message["name"] == "LiquidAlphaDisabled" + + # Enabled liquid alpha on the subnet + assert await async_sudo_set_hyperparameter_bool( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_liquid_alpha_enabled", + value=True, + netuid=netuid, + ), "Unable to enable liquid alpha" + + assert (await async_subtensor.subnets.get_subnet_hyperparameters( + netuid, + )).liquid_alpha_enabled, "Failed to enable liquid alpha" + + # Attempt to set alpha high & low after enabling the hyperparameter + alpha_values = "26001, 54099" + call_params = liquid_alpha_call_params(netuid, alpha_values) + assert await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_alpha_values", + call_params=call_params, + ), "Unable to set alpha_values" + + sn_hps = await async_subtensor.subnets.get_subnet_hyperparameters(netuid) + assert sn_hps.alpha_high == 54099, "Failed to set alpha high" + assert sn_hps.alpha_low == 26001, "Failed to set alpha low" + + # Testing alpha high upper and lower bounds + + # 1. Test setting Alpha_high too low + alpha_high_too_low = 87 + + call_params = liquid_alpha_call_params(netuid, f"6553, {alpha_high_too_low}") + result, error_message = await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_alpha_values", + call_params=call_params, + return_error_message=True, + ) + + assert result is False, "Able to set incorrect alpha_high value" + assert error_message["name"] == "AlphaHighTooLow" + + # 2. Test setting Alpha_high too high + alpha_high_too_high = u16_max + 1 # One more than the max acceptable value + call_params = liquid_alpha_call_params(netuid, f"6553, {alpha_high_too_high}") + try: + await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_alpha_values", + call_params=call_params, + return_error_message=True, + ) + except Exception as e: + assert str(e) == "65536 out of range for u16", f"Unexpected error: {e}" + + # Testing alpha low upper and lower bounds + + # 1. Test setting Alpha_low too low + alpha_low_too_low = 0 + call_params = liquid_alpha_call_params(netuid, f"{alpha_low_too_low}, 53083") + result, error_message = await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_alpha_values", + call_params=call_params, + return_error_message=True, + ) + assert result is False, "Able to set incorrect alpha_low value" + assert error_message["name"] == "AlphaLowOutOfRange" + + # 2. Test setting Alpha_low too high + alpha_low_too_high = 53084 + call_params = liquid_alpha_call_params(netuid, f"{alpha_low_too_high}, 53083") + result, error_message = await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_alpha_values", + call_params=call_params, + return_error_message=True, + ) + assert result is False, "Able to set incorrect alpha_low value" + assert error_message["name"] == "AlphaLowOutOfRange" + + # Setting normal alpha values + alpha_values = "6553, 53083" + call_params = liquid_alpha_call_params(netuid, alpha_values) + assert await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_alpha_values", + call_params=call_params, + ), "Unable to set liquid alpha values" + + sn_hps = await async_subtensor.subnets.get_subnet_hyperparameters(netuid) + assert sn_hps.alpha_high == 53083, "Failed to set alpha high" + assert sn_hps.alpha_low == 6553, "Failed to set alpha low" + + # Disable Liquid Alpha + assert await async_sudo_set_hyperparameter_bool( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_liquid_alpha_enabled", + value=False, + netuid=netuid, + ), "Unable to disable liquid alpha" + + assert ( + (await async_subtensor.subnets.get_subnet_hyperparameters(netuid)).liquid_alpha_enabled + is False + ), "Failed to disable liquid alpha" + + logging.console.info("✅ Passed [blue]test_liquid_alpha[/blue]") From 5c6a4bdea747343ea653616c6aedcdd67ef65c50 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:37:32 -0700 Subject: [PATCH 30/86] ruff --- tests/e2e_tests/test_incentive.py | 6 +++--- tests/e2e_tests/test_liquid_alpha.py | 22 +++++++++++++-------- tests/e2e_tests/utils/chain_interactions.py | 5 +++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 47b3cf2825..70cda9b48b 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -247,9 +247,9 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal await async_wait_epoch(async_subtensor, alice_subnet_netuid) # Get current miner/validator stats - alice_neuron = ( - await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid) - )[0] + alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[ + 0 + ] assert alice_neuron.validator_permit is True assert alice_neuron.dividends == 0 diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index b92ab74fe2..582f564c31 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -8,7 +8,10 @@ sudo_set_hyperparameter_bool, sudo_set_hyperparameter_values, ) -from tests.e2e_tests.utils.e2e_test_utils import async_wait_to_start_call, wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) def liquid_alpha_call_params(netuid: int, alpha_values: str): @@ -242,7 +245,9 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): ), "Unable to stake to Alice neuron" # Assert liquid alpha is disabled - assert (await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid)).liquid_alpha_enabled is False, "Liquid alpha is enabled by default" + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) + ).liquid_alpha_enabled is False, "Liquid alpha is enabled by default" # Attempt to set alpha high/low while disabled (should fail) alpha_values = "6553, 53083" @@ -266,9 +271,11 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): netuid=netuid, ), "Unable to enable liquid alpha" - assert (await async_subtensor.subnets.get_subnet_hyperparameters( - netuid, - )).liquid_alpha_enabled, "Failed to enable liquid alpha" + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters( + netuid, + ) + ).liquid_alpha_enabled, "Failed to enable liquid alpha" # Attempt to set alpha high & low after enabling the hyperparameter alpha_values = "26001, 54099" @@ -367,8 +374,7 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): ), "Unable to disable liquid alpha" assert ( - (await async_subtensor.subnets.get_subnet_hyperparameters(netuid)).liquid_alpha_enabled - is False - ), "Failed to disable liquid alpha" + await async_subtensor.subnets.get_subnet_hyperparameters(netuid) + ).liquid_alpha_enabled is False, "Failed to disable liquid alpha" logging.console.info("✅ Passed [blue]test_liquid_alpha[/blue]") diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index c3ced7527f..edbb8305bc 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -113,7 +113,9 @@ async def async_sudo_set_hyperparameter_values( call_function=call_function, call_params=call_params, ) - extrinsic = await substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey) + extrinsic = await substrate.create_signed_extrinsic( + call=call, keypair=wallet.coldkey + ) response = await substrate.submit_extrinsic( extrinsic=extrinsic, wait_for_inclusion=True, @@ -126,7 +128,6 @@ async def async_sudo_set_hyperparameter_values( return await response.is_success - async def wait_epoch(subtensor: "SubtensorApi", netuid: int = 1, **kwargs): """ Waits for the next epoch to start on a specific subnet. From 5bb4d52a2c0a57400507b4226b18bfcb5c2b4c33 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:38:57 -0700 Subject: [PATCH 31/86] logging --- tests/e2e_tests/test_liquid_alpha.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 582f564c31..3306deb072 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -217,7 +217,7 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_liquid_alpha[/blue]") + logging.console.info("Testing [blue]test_liquid_alpha_async[/blue]") u16_max = 65535 netuid = 2 @@ -377,4 +377,4 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): await async_subtensor.subnets.get_subnet_hyperparameters(netuid) ).liquid_alpha_enabled is False, "Failed to disable liquid alpha" - logging.console.info("✅ Passed [blue]test_liquid_alpha[/blue]") + logging.console.info("✅ Passed [blue]test_liquid_alpha_async[/blue]") From adc3065efbb2031c0d3495f401ad1ded06ae663b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 12:07:58 -0700 Subject: [PATCH 32/86] async test for `tests/e2e_tests/test_liquidity.py` --- tests/e2e_tests/test_liquidity.py | 325 +++++++++++++++++++++++++++++- 1 file changed, 318 insertions(+), 7 deletions(-) diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index e037d81fba..c99b23338d 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -1,12 +1,15 @@ import pytest from bittensor import Balance, logging -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) from bittensor.utils.liquidity import LiquidityPosition @pytest.mark.asyncio -async def test_liquidity(local_chain, subtensor, alice_wallet, bob_wallet): +async def test_liquidity(subtensor, alice_wallet, bob_wallet): """ Tests the liquidity mechanism @@ -22,17 +25,22 @@ async def test_liquidity(local_chain, subtensor, alice_wallet, bob_wallet): 9. Remove all stake from Alice and check `get_liquidity_list` return new liquidity positions with 0 fees_tao. 10. Remove all liquidity positions and check `get_liquidity_list` return empty list. """ + logging.console.info("Testing [blue]test_liquidity[/blue]") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Make sure `get_liquidity_list` return None if SN doesn't exist assert ( - subtensor.get_liquidity_list(wallet=alice_wallet, netuid=alice_subnet_netuid) + subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) is None ), "❌ `get_liquidity_list` is not None for unexisting subnet." # Register root as Alice - assert subtensor.register_subnet(alice_wallet), "❌ Unable to register the subnet" + assert subtensor.subnets.register_subnet(alice_wallet), ( + "❌ Unable to register the subnet" + ) # Verify subnet 2 created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -80,7 +88,7 @@ async def test_liquidity(local_chain, subtensor, alice_wallet, bob_wallet): # wait for the next block to give the chain time to update the stake subtensor.wait_for_block() - current_balance = subtensor.get_balance(alice_wallet.hotkey.ss58_address) + current_balance = subtensor.wallets.get_balance(alice_wallet.hotkey.ss58_address) current_sn_stake = subtensor.staking.get_stake_info_for_coldkey( coldkey_ss58=alice_wallet.coldkey.ss58_address ) @@ -197,7 +205,7 @@ async def test_liquidity(local_chain, subtensor, alice_wallet, bob_wallet): # wait for the next block to give the chain time to update the stake subtensor.wait_for_block() - current_balance = subtensor.get_balance(alice_wallet.hotkey.ss58_address) + current_balance = subtensor.wallets.get_balance(alice_wallet.hotkey.ss58_address) current_sn_stake = subtensor.staking.get_stake_info_for_coldkey( coldkey_ss58=alice_wallet.coldkey.ss58_address ) @@ -289,3 +297,306 @@ async def test_liquidity(local_chain, subtensor, alice_wallet, bob_wallet): ) == [] ), "❌ Not all liquidity positions removed." + + logging.console.info("✅ Passed [blue]test_liquidity[/blue]") + + +@pytest.mark.asyncio +async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): + """ + ASync tests the liquidity mechanism + + Steps: + 1. Check `get_liquidity_list` return None if SN doesn't exist. + 2. Register a subnet through Alice. + 3. Make sure `get_liquidity_list` return None without activ SN. + 4. Wait until start call availability and do this call. + 5. Add liquidity to the subnet and check `get_liquidity_list` return liquidity positions. + 6. Modify liquidity and check `get_liquidity_list` return new liquidity positions with modified liquidity value. + 7. Add second liquidity position and check `get_liquidity_list` return new liquidity positions with 0 index. + 8. Add stake from Bob to Alice and check `get_liquidity_list` return new liquidity positions with fees_tao. + 9. Remove all stake from Alice and check `get_liquidity_list` return new liquidity positions with 0 fees_tao. + 10. Remove all liquidity positions and check `get_liquidity_list` return empty list. + """ + logging.console.info("Testing [blue]test_liquidity_async[/blue]") + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Make sure `get_liquidity_list` return None if SN doesn't exist + assert ( + await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + is None + ), "❌ `get_liquidity_list` is not None for unexisting subnet." + + # Register root as Alice + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "❌ Unable to register the subnet" + ) + + # Verify subnet 2 created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + f"❌ Subnet {alice_subnet_netuid} wasn't created successfully" + ) + + # Make sure `get_liquidity_list` return None without activ SN + assert ( + await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + is None + ), "❌ `get_liquidity_list` is not None when no activ subnet." + + # Wait until start call availability and do this call + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + # Make sure `get_liquidity_list` return None without activ SN + assert ( + await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + == [] + ), "❌ `get_liquidity_list` is not empty list before fist liquidity add." + + # enable user liquidity in SN + success, message = await async_subtensor.extrinsics.toggle_user_liquidity( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + enable=True, + ) + assert success, message + assert message == "", "❌ Cannot enable user liquidity." + + # Add steak to call add_liquidity + assert await async_subtensor.extrinsics.add_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1), + wait_for_inclusion=True, + wait_for_finalization=True, + ), "❌ Cannot cannot add stake to Alice from Alice." + + # wait for the next block to give the chain time to update the stake + await async_subtensor.wait_for_block() + + current_balance = await async_subtensor.wallets.get_balance( + alice_wallet.hotkey.ss58_address + ) + current_sn_stake = await async_subtensor.staking.get_stake_info_for_coldkey( + coldkey_ss58=alice_wallet.coldkey.ss58_address + ) + logging.console.info( + f"Alice balance: {current_balance} and stake: {current_sn_stake}" + ) + + # Add liquidity + success, message = await async_subtensor.extrinsics.add_liquidity( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + liquidity=Balance.from_tao(1), + price_low=Balance.from_tao(1.7), + price_high=Balance.from_tao(1.8), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success, message + assert message == "", "❌ Cannot add liquidity." + + # Get liquidity + liquidity_positions = await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + + assert len(liquidity_positions) == 1, ( + "❌ liquidity_positions has more than one element." + ) + + # Check if liquidity is correct + liquidity_position = liquidity_positions[0] + assert liquidity_position == LiquidityPosition( + id=2, + price_low=liquidity_position.price_low, + price_high=liquidity_position.price_high, + liquidity=Balance.from_tao(1), + fees_tao=Balance.from_tao(0), + fees_alpha=Balance.from_tao(0, netuid=alice_subnet_netuid), + netuid=alice_subnet_netuid, + ), "❌ `get_liquidity_list` still empty list after liquidity add." + + # Modify liquidity position with positive value + success, message = await async_subtensor.extrinsics.modify_liquidity( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + position_id=liquidity_position.id, + liquidity_delta=Balance.from_tao(20), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success, message + assert message == "", "❌ cannot modify liquidity position." + + liquidity_positions = await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + + assert len(liquidity_positions) == 1, ( + "❌ liquidity_positions has more than one element." + ) + liquidity_position = liquidity_positions[0] + + assert liquidity_position == LiquidityPosition( + id=2, + price_low=liquidity_position.price_low, + price_high=liquidity_position.price_high, + liquidity=Balance.from_tao(21), + fees_tao=Balance.from_tao(0), + fees_alpha=Balance.from_tao(0, netuid=alice_subnet_netuid), + netuid=alice_subnet_netuid, + ) + + # Modify liquidity position with negative value + success, message = await async_subtensor.extrinsics.modify_liquidity( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + position_id=liquidity_position.id, + liquidity_delta=-Balance.from_tao(11), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success, message + assert message == "", "❌ cannot modify liquidity position." + + liquidity_positions = await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + + assert len(liquidity_positions) == 1, ( + "❌ liquidity_positions has more than one element." + ) + liquidity_position = liquidity_positions[0] + + assert liquidity_position == LiquidityPosition( + id=2, + price_low=liquidity_position.price_low, + price_high=liquidity_position.price_high, + liquidity=Balance.from_tao(10), + fees_tao=Balance.from_tao(0), + fees_alpha=Balance.from_tao(0, netuid=alice_subnet_netuid), + netuid=alice_subnet_netuid, + ) + + # Add stake from Bob to Alice + assert await async_subtensor.extrinsics.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1000), + wait_for_inclusion=True, + wait_for_finalization=True, + ), "❌ Cannot add stake from Bob to Alice." + + # wait for the next block to give the chain time to update the stake + await async_subtensor.wait_for_block() + + current_balance = await async_subtensor.wallets.get_balance( + alice_wallet.hotkey.ss58_address + ) + current_sn_stake = await async_subtensor.staking.get_stake_info_for_coldkey( + coldkey_ss58=alice_wallet.coldkey.ss58_address + ) + logging.console.info( + f"Alice balance: {current_balance} and stake: {current_sn_stake}" + ) + + # Add second liquidity position + success, message = await async_subtensor.extrinsics.add_liquidity( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + liquidity=Balance.from_tao(150), + price_low=Balance.from_tao(0.8), + price_high=Balance.from_tao(1.2), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success, message + assert message == "", "❌ Cannot add liquidity." + + liquidity_positions = await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + + assert len(liquidity_positions) == 2, ( + f"❌ liquidity_positions should have 2 elements, but has only {len(liquidity_positions)} element." + ) + + # All new liquidity inserts on the 0 index + liquidity_position_second = liquidity_positions[0] + assert liquidity_position_second == LiquidityPosition( + id=3, + price_low=liquidity_position_second.price_low, + price_high=liquidity_position_second.price_high, + liquidity=Balance.from_tao(150), + fees_tao=Balance.from_tao(0), + fees_alpha=Balance.from_tao(0, netuid=alice_subnet_netuid), + netuid=alice_subnet_netuid, + ) + + liquidity_position_first = liquidity_positions[1] + assert liquidity_position_first == LiquidityPosition( + id=2, + price_low=liquidity_position_first.price_low, + price_high=liquidity_position_first.price_high, + liquidity=Balance.from_tao(10), + fees_tao=liquidity_position_first.fees_tao, + fees_alpha=Balance.from_tao(0, netuid=alice_subnet_netuid), + netuid=alice_subnet_netuid, + ) + # After adding stake alice liquidity position has a fees_tao bc of high price + assert liquidity_position_first.fees_tao > Balance.from_tao(0) + + # Bob remove all stake from alice + assert await async_subtensor.extrinsics.unstake_all( + wallet=bob_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Check that fees_alpha comes too after all unstake + liquidity_position_first = ( + await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + )[1] + assert liquidity_position_first.fees_tao > Balance.from_tao(0) + assert liquidity_position_first.fees_alpha > Balance.from_tao( + 0, alice_subnet_netuid + ) + + # Remove all liquidity positions + for p in liquidity_positions: + success, message = await async_subtensor.extrinsics.remove_liquidity( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + position_id=p.id, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success, message + assert message == "", "❌ Cannot remove liquidity." + + # Make sure all liquidity positions removed + assert ( + await async_subtensor.subnets.get_liquidity_list( + wallet=alice_wallet, netuid=alice_subnet_netuid + ) + == [] + ), "❌ Not all liquidity positions removed." + + logging.console.info("✅ Passed [blue]test_liquidity_async[/blue]") From f0e509d6b9d74717d7be1f961505757a53932bce Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 12:56:29 -0700 Subject: [PATCH 33/86] async test for `tests/e2e_tests/test_metagraph.py` --- tests/e2e_tests/test_metagraph.py | 792 ++++++++++++++++++++++++++++-- 1 file changed, 751 insertions(+), 41 deletions(-) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 0e2fc4723c..fba4e6fea7 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -2,12 +2,16 @@ import re import shutil import time +import pytest from bittensor.core.chain_data.metagraph_info import MetagraphInfo from bittensor.core.chain_data import SelectiveMetagraphIndex from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) NULL_KEY = tuple(bytearray(32)) @@ -47,16 +51,17 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_metagraph_command") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + logging.console.info("Testing [blue]test_metagraph[/blue]") + + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 logging.console.info("Register the subnet through Alice") - assert subtensor.register_subnet(alice_wallet, True, True), ( + assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( "Unable to register the subnet" ) logging.console.info("Verify subnet was created successfully") - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -64,18 +69,18 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) logging.console.info("Initialize metagraph") - metagraph = subtensor.metagraph(netuid=alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(netuid=alice_subnet_netuid) logging.console.info("Assert metagraph has only Alice (owner)") assert len(metagraph.uids) == 1, "Metagraph doesn't have exactly 1 neuron" logging.console.info("Register Bob to the subnet") - assert subtensor.burned_register(bob_wallet, alice_subnet_netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( "Unable to register Bob as a neuron" ) logging.console.info("Refresh the metagraph") - metagraph.sync(subtensor=subtensor) + metagraph.sync(subtensor=subtensor._subtensor) logging.console.info("Assert metagraph has Alice and Bob neurons") assert len(metagraph.uids) == 2, "Metagraph doesn't have exactly 2 neurons" @@ -91,12 +96,12 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): assert len(metagraph.addresses) == 2, "Metagraph doesn't have exactly 2 address" logging.console.info("Fetch UID of Bob") - uid = subtensor.get_uid_for_hotkey_on_subnet( + uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid ) logging.console.info("Fetch neuron info of Bob through subtensor and metagraph") - neuron_info_bob = subtensor.neuron_for_uid(uid, netuid=alice_subnet_netuid) + neuron_info_bob = subtensor.neurons.neuron_for_uid(uid, netuid=alice_subnet_netuid) metagraph_dict = neuron_to_dict(metagraph.neurons[uid]) subtensor_dict = neuron_to_dict(neuron_info_bob) @@ -107,14 +112,14 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): ) logging.console.info("Create pre_dave metagraph for future verifications") - metagraph_pre_dave = subtensor.metagraph(netuid=alice_subnet_netuid) + metagraph_pre_dave = subtensor.metagraphs.metagraph(netuid=alice_subnet_netuid) logging.console.info("Register Dave as a neuron") - assert subtensor.burned_register(dave_wallet, alice_subnet_netuid), ( + assert subtensor.subnets.burned_register(dave_wallet, alice_subnet_netuid), ( "Unable to register Dave as a neuron" ) - metagraph.sync(subtensor=subtensor) + metagraph.sync(subtensor=subtensor._subtensor) logging.console.info("Assert metagraph now includes Dave's neuron") assert len(metagraph.uids) == 3, ( @@ -132,9 +137,11 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): logging.console.info("Add stake by Bob") tao = Balance.from_tao(10_000) - alpha, _ = subtensor.subnet(alice_subnet_netuid).tao_to_alpha_with_slippage(tao) - assert subtensor.add_stake( - bob_wallet, + alpha, _ = subtensor.subnets.subnet(alice_subnet_netuid).tao_to_alpha_with_slippage( + tao + ) + assert subtensor.staking.add_stake( + wallet=bob_wallet, netuid=alice_subnet_netuid, amount=tao, wait_for_inclusion=True, @@ -142,7 +149,7 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): ), "Failed to add stake for Bob" logging.console.info("Assert stake is added after updating metagraph") - metagraph.sync(subtensor=subtensor) + metagraph.sync(subtensor=subtensor._subtensor) assert 0.95 < metagraph.neurons[1].stake.rao / alpha.rao < 1.05, ( "Bob's stake not updated in metagraph" ) @@ -184,7 +191,177 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): "Neurons don't match after save and load" ) - logging.console.info("✅ Passed test_metagraph") + logging.console.info("✅ Passed [blue]test_metagraph[/blue]") + + +@pytest.mark.asyncio +async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): + """ + Async tests the metagraph + + Steps: + 1. Register a subnet through Alice + 2. Assert metagraph's initial state + 3. Register Bob and validate info in metagraph + 4. Fetch neuron info of Bob through subtensor & metagraph and verify + 5. Register Dave and validate info in metagraph + 6. Verify low balance stake fails & add stake thru Bob and verify + 7. Load pre_dave metagraph from latest save and verify both instances + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing [blue]test_metagraph_async[/blue]") + + async with async_subtensor: + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + logging.console.info("Register the subnet through Alice") + assert await async_subtensor.subnets.register_subnet( + alice_wallet, True, True + ), "Unable to register the subnet" + + logging.console.info("Verify subnet was created successfully") + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + logging.console.info("Make sure we passed start_call limit (10 blocks)") + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + logging.console.info("Initialize metagraph") + metagraph = await async_subtensor.metagraphs.metagraph( + netuid=alice_subnet_netuid + ) + + logging.console.info("Assert metagraph has only Alice (owner)") + assert len(metagraph.uids) == 1, "Metagraph doesn't have exactly 1 neuron" + + logging.console.info("Register Bob to the subnet") + assert await async_subtensor.subnets.burned_register( + bob_wallet, alice_subnet_netuid + ), "Unable to register Bob as a neuron" + + logging.console.info("Refresh the metagraph") + await metagraph.sync(subtensor=async_subtensor._subtensor) + + logging.console.info("Assert metagraph has Alice and Bob neurons") + assert len(metagraph.uids) == 2, "Metagraph doesn't have exactly 2 neurons" + assert metagraph.hotkeys[0] == alice_wallet.hotkey.ss58_address, ( + "Alice's hotkey doesn't match in metagraph" + ) + assert metagraph.hotkeys[1] == bob_wallet.hotkey.ss58_address, ( + "Bob's hotkey doesn't match in metagraph" + ) + assert len(metagraph.coldkeys) == 2, "Metagraph doesn't have exactly 2 coldkey" + assert metagraph.n.max() == 2, "Metagraph's max n is not 2" + assert metagraph.n.min() == 2, "Metagraph's min n is not 2" + assert len(metagraph.addresses) == 2, "Metagraph doesn't have exactly 2 address" + + logging.console.info("Fetch UID of Bob") + uid = await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( + bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid + ) + + logging.console.info("Fetch neuron info of Bob through subtensor and metagraph") + neuron_info_bob = await async_subtensor.neurons.neuron_for_uid( + uid, netuid=alice_subnet_netuid + ) + + metagraph_dict = neuron_to_dict(metagraph.neurons[uid]) + subtensor_dict = neuron_to_dict(neuron_info_bob) + + logging.console.info("Verify neuron info is the same in both objects") + assert metagraph_dict == subtensor_dict, ( + "Neuron info of Bob doesn't match b/w metagraph & subtensor" + ) + + logging.console.info("Create pre_dave metagraph for future verifications") + metagraph_pre_dave = await async_subtensor.metagraphs.metagraph( + netuid=alice_subnet_netuid + ) + + logging.console.info("Register Dave as a neuron") + assert await async_subtensor.subnets.burned_register( + dave_wallet, alice_subnet_netuid + ), "Unable to register Dave as a neuron" + + await metagraph.sync(subtensor=async_subtensor._subtensor) + + logging.console.info("Assert metagraph now includes Dave's neuron") + assert len(metagraph.uids) == 3, ( + "Metagraph doesn't have exactly 3 neurons post Dave" + ) + assert metagraph.hotkeys[2] == dave_wallet.hotkey.ss58_address, ( + "Neuron's hotkey in metagraph doesn't match" + ) + assert len(metagraph.coldkeys) == 3, ( + "Metagraph doesn't have exactly 3 coldkeys post Dave" + ) + assert metagraph.n.max() == 3, "Metagraph's max n is not 3 post Dave" + assert metagraph.n.min() == 3, "Metagraph's min n is not 3 post Dave" + assert len(metagraph.addresses) == 3, ( + "Metagraph doesn't have 3 addresses post Dave" + ) + + logging.console.info("Add stake by Bob") + tao = Balance.from_tao(10_000) + alpha, _ = ( + await async_subtensor.subnets.subnet(alice_subnet_netuid) + ).tao_to_alpha_with_slippage(tao) + assert await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + amount=tao, + wait_for_inclusion=True, + wait_for_finalization=True, + ), "Failed to add stake for Bob" + + logging.console.info("Assert stake is added after updating metagraph") + await metagraph.sync(subtensor=async_subtensor._subtensor) + assert 0.95 < metagraph.neurons[1].stake.rao / alpha.rao < 1.05, ( + "Bob's stake not updated in metagraph" + ) + + logging.console.info("Test the save() and load() mechanism") + # We save the metagraph and pre_dave loads it + # We do this in the /tmp dir to avoid interfering or interacting with user data + metagraph_save_root_dir = ["/", "tmp", "bittensor-e2e", "metagraphs"] + try: + os.makedirs(os.path.join(*metagraph_save_root_dir), exist_ok=True) + metagraph.save(root_dir=metagraph_save_root_dir) + time.sleep(3) + metagraph_pre_dave.load(root_dir=metagraph_save_root_dir) + finally: + shutil.rmtree(os.path.join(*metagraph_save_root_dir)) + + logging.console.info("Ensure data is synced between two metagraphs") + assert len(metagraph.uids) == len(metagraph_pre_dave.uids), ( + "UID count mismatch after save and load" + ) + assert (metagraph.uids == metagraph_pre_dave.uids).all(), ( + "UIDs don't match after save and load" + ) + + assert len(metagraph.axons) == len(metagraph_pre_dave.axons), ( + "Axon count mismatch after save and load" + ) + assert metagraph.axons[1].hotkey == metagraph_pre_dave.axons[1].hotkey, ( + "Axon hotkey mismatch after save and load" + ) + assert metagraph.axons == metagraph_pre_dave.axons, ( + "Axons don't match after save and load" + ) + + assert len(metagraph.neurons) == len(metagraph_pre_dave.neurons), ( + "Neuron count mismatch after save and load" + ) + assert metagraph.neurons == metagraph_pre_dave.neurons, ( + "Neurons don't match after save and load" + ) + + logging.console.info("✅ Passed [blue]test_metagraph_async[/blue]") def test_metagraph_info(subtensor, alice_wallet, bob_wallet): @@ -195,11 +372,12 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ + logging.console.info("Testing [blue]test_metagraph_info[/blue]") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 - subtensor.register_subnet(alice_wallet, True, True) + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet, True, True) - metagraph_info = subtensor.get_metagraph_info(netuid=1, block=1) + metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=1, block=1) expected_metagraph_info = MetagraphInfo( netuid=1, @@ -294,7 +472,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): assert metagraph_info == expected_metagraph_info - metagraph_infos = subtensor.get_all_metagraphs_info(block=1) + metagraph_infos = subtensor.metagraphs.get_all_metagraphs_info(block=1) expected_metagraph_infos = [ MetagraphInfo( @@ -379,14 +557,280 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - assert subtensor.burned_register( + assert subtensor.subnets.burned_register( + bob_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=alice_subnet_netuid) + + assert metagraph_info.num_uids == 2 + assert metagraph_info.hotkeys == [ + alice_wallet.hotkey.ss58_address, + bob_wallet.hotkey.ss58_address, + ] + assert metagraph_info.coldkeys == [ + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ] + assert metagraph_info.tao_dividends_per_hotkey == [ + ( + alice_wallet.hotkey.ss58_address, + metagraph_info.tao_dividends_per_hotkey[0][1], + ), + (bob_wallet.hotkey.ss58_address, metagraph_info.tao_dividends_per_hotkey[1][1]), + ] + assert metagraph_info.alpha_dividends_per_hotkey == [ + ( + alice_wallet.hotkey.ss58_address, + metagraph_info.alpha_dividends_per_hotkey[0][1], + ), + ( + bob_wallet.hotkey.ss58_address, + metagraph_info.alpha_dividends_per_hotkey[1][1], + ), + ] + + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 + assert subtensor.subnets.register_subnet( + alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + block = subtensor.chain.get_current_block() + metagraph_info = subtensor.metagraphs.get_metagraph_info( + netuid=alice_subnet_netuid, block=block + ) + + assert metagraph_info.owner_coldkey == alice_wallet.hotkey.ss58_address + assert metagraph_info.owner_hotkey == alice_wallet.coldkey.ss58_address + + metagraph_infos = subtensor.metagraphs.get_all_metagraphs_info(block) + + assert len(metagraph_infos) == 4 + assert metagraph_infos[-1] == metagraph_info + + # non-existed subnet + metagraph_info = subtensor.metagraphs.get_metagraph_info( + netuid=alice_subnet_netuid + 1 + ) + + assert metagraph_info is None + + logging.console.info("✅ Passed [blue]test_metagraph_info[/blue]") + + +@pytest.mark.asyncio +async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): + """ + Async tests: + - Check MetagraphInfo + - Register Neuron + - Register Subnet + - Check MetagraphInfo is updated + """ + logging.console.info("Testing [blue]test_metagraph_info_async[/blue]") + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True) + + metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( + netuid=1, block=1 + ) + + expected_metagraph_info = MetagraphInfo( + netuid=1, + name="apex", + symbol="α", + identity=None, + network_registered_at=0, + owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + block=1, + tempo=100, + last_step=0, + blocks_since_last_step=1, + subnet_emission=Balance(0), + alpha_in=Balance.from_tao(10).set_unit(1), + alpha_out=Balance.from_tao(1).set_unit(1), + tao_in=Balance.from_tao(10), + alpha_out_emission=Balance(0).set_unit(1), + alpha_in_emission=Balance(0).set_unit(1), + tao_in_emission=Balance(0), + pending_alpha_emission=Balance(0).set_unit(1), + pending_root_emission=Balance(0), + subnet_volume=Balance(0).set_unit(1), + moving_price=Balance(0), + rho=10, + kappa=32767, + min_allowed_weights=0.0, + max_weights_limit=1.0, + weights_version=0, + weights_rate_limit=100, + activity_cutoff=5000, + max_validators=64, + num_uids=1, + max_uids=256, + burn=Balance.from_tao(0.1), + difficulty=5.421010862427522e-13, + registration_allowed=True, + pow_registration_allowed=True, + immunity_period=4096, + min_difficulty=5.421010862427522e-13, + max_difficulty=0.25, + min_burn=Balance.from_tao(0.0005), + max_burn=Balance.from_tao(100), + adjustment_alpha=0.0, + adjustment_interval=100, + target_regs_per_interval=2, + max_regs_per_block=1, + serving_rate_limit=50, + commit_reveal_weights_enabled=True, + commit_reveal_period=1, + liquid_alpha_enabled=False, + alpha_high=0.9000076295109484, + alpha_low=0.7000076295109483, + bonds_moving_avg=4.87890977618477e-14, + hotkeys=["5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"], + coldkeys=["5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM"], + identities=[None], + axons=( + { + "block": 0, + "version": 0, + "ip": 0, + "port": 0, + "ip_type": 0, + "protocol": 0, + "placeholder1": 0, + "placeholder2": 0, + }, + ), + active=(True,), + validator_permit=(False,), + pruning_score=[0.0], + last_update=(0,), + emission=[Balance(0).set_unit(1)], + dividends=[0.0], + incentives=[0.0], + consensus=[0.0], + trust=[0.0], + rank=[0.0], + block_at_registration=(0,), + alpha_stake=[Balance.from_tao(1.0).set_unit(1)], + tao_stake=[Balance(0)], + total_stake=[Balance.from_tao(1.0).set_unit(1)], + tao_dividends_per_hotkey=[ + ("5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", Balance(0)) + ], + alpha_dividends_per_hotkey=[ + ("5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", Balance(0).set_unit(1)) + ], + validators=None, + ) + + assert metagraph_info == expected_metagraph_info + + metagraph_infos = await async_subtensor.metagraphs.get_all_metagraphs_info(block=1) + + expected_metagraph_infos = [ + MetagraphInfo( + netuid=0, + name="root", + symbol="Τ", + identity=None, + network_registered_at=0, + owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + block=1, + tempo=100, + last_step=0, + blocks_since_last_step=1, + subnet_emission=Balance(0), + alpha_in=Balance(0), + alpha_out=Balance(0), + tao_in=Balance(0), + alpha_out_emission=Balance(0), + alpha_in_emission=Balance(0), + tao_in_emission=Balance(0), + pending_alpha_emission=Balance(0), + pending_root_emission=Balance(0), + subnet_volume=Balance(0), + moving_price=Balance(0), + rho=10, + kappa=32767, + min_allowed_weights=0.0, + max_weights_limit=1.0, + weights_version=0, + weights_rate_limit=100, + activity_cutoff=5000, + max_validators=64, + num_uids=0, + max_uids=64, + burn=Balance.from_tao(0.1), + difficulty=5.421010862427522e-13, + registration_allowed=True, + pow_registration_allowed=True, + immunity_period=4096, + min_difficulty=5.421010862427522e-13, + max_difficulty=0.25, + min_burn=Balance.from_tao(0.0005), + max_burn=Balance.from_tao(100), + adjustment_alpha=0.0, + adjustment_interval=100, + target_regs_per_interval=1, + max_regs_per_block=1, + serving_rate_limit=50, + commit_reveal_weights_enabled=True, + commit_reveal_period=1, + liquid_alpha_enabled=False, + alpha_high=0.9000076295109484, + alpha_low=0.7000076295109483, + bonds_moving_avg=4.87890977618477e-14, + hotkeys=[], + coldkeys=[], + identities={}, + axons=(), + active=(), + validator_permit=(), + pruning_score=[], + last_update=(), + emission=[], + dividends=[], + incentives=[], + consensus=[], + trust=[], + rank=[], + block_at_registration=(), + alpha_stake=[], + tao_stake=[], + total_stake=[], + tao_dividends_per_hotkey=[], + alpha_dividends_per_hotkey=[], + validators=None, + ), + metagraph_info, + ] + + assert metagraph_infos == expected_metagraph_infos + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + assert await async_subtensor.subnets.burned_register( bob_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - metagraph_info = subtensor.get_metagraph_info(netuid=alice_subnet_netuid) + metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( + netuid=alice_subnet_netuid + ) assert metagraph_info.num_uids == 2 assert metagraph_info.hotkeys == [ @@ -415,32 +859,34 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): ), ] - alice_subnet_netuid = subtensor.get_total_subnets() # 3 - assert subtensor.register_subnet( + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 + assert await async_subtensor.subnets.register_subnet( alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ) - block = subtensor.get_current_block() - metagraph_info = subtensor.get_metagraph_info( + block = await async_subtensor.chain.get_current_block() + metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( netuid=alice_subnet_netuid, block=block ) assert metagraph_info.owner_coldkey == alice_wallet.hotkey.ss58_address assert metagraph_info.owner_hotkey == alice_wallet.coldkey.ss58_address - metagraph_infos = subtensor.get_all_metagraphs_info(block) + metagraph_infos = await async_subtensor.metagraphs.get_all_metagraphs_info(block) assert len(metagraph_infos) == 4 assert metagraph_infos[-1] == metagraph_info # non-existed subnet - metagraph_info = subtensor.get_metagraph_info(netuid=alice_subnet_netuid + 1) + metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( + netuid=alice_subnet_netuid + 1 + ) assert metagraph_info is None - logging.console.info("✅ Passed test_metagraph_info") + logging.console.info("✅ Passed [blue]test_metagraph_info_async[/blue]") def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): @@ -451,9 +897,10 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ + logging.console.info("Testing [blue]test_metagraph_info_with_indexes[/blue]") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 - subtensor.register_subnet(alice_wallet, True, True) + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet, True, True) field_indices = [ SelectiveMetagraphIndex.Name, @@ -463,7 +910,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): SelectiveMetagraphIndex.Axons, ] - metagraph_info = subtensor.get_metagraph_info( + metagraph_info = subtensor.metagraphs.get_metagraph_info( netuid=alice_subnet_netuid, field_indices=field_indices ) @@ -556,7 +1003,245 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - assert subtensor.burned_register( + assert subtensor.subnets.burned_register( + bob_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + fields = [ + SelectiveMetagraphIndex.Name, + SelectiveMetagraphIndex.Active, + SelectiveMetagraphIndex.OwnerHotkey, + SelectiveMetagraphIndex.OwnerColdkey, + SelectiveMetagraphIndex.Axons, + ] + + metagraph_info = subtensor.metagraphs.get_metagraph_info( + netuid=alice_subnet_netuid, field_indices=fields + ) + + assert metagraph_info == MetagraphInfo( + netuid=alice_subnet_netuid, + name="omron", + owner_hotkey=alice_wallet.hotkey.ss58_address, + owner_coldkey=alice_wallet.coldkey.ss58_address, + active=(True, True), + axons=( + { + "block": 0, + "ip": 0, + "ip_type": 0, + "placeholder1": 0, + "placeholder2": 0, + "port": 0, + "protocol": 0, + "version": 0, + }, + { + "block": 0, + "ip": 0, + "ip_type": 0, + "placeholder1": 0, + "placeholder2": 0, + "port": 0, + "protocol": 0, + "version": 0, + }, + ), + symbol=None, + identity=None, + network_registered_at=None, + block=None, + tempo=None, + last_step=None, + blocks_since_last_step=None, + subnet_emission=None, + alpha_in=None, + alpha_out=None, + tao_in=None, + alpha_out_emission=None, + alpha_in_emission=None, + tao_in_emission=None, + pending_alpha_emission=None, + pending_root_emission=None, + subnet_volume=None, + moving_price=None, + rho=None, + kappa=None, + min_allowed_weights=None, + max_weights_limit=None, + weights_version=None, + weights_rate_limit=None, + activity_cutoff=None, + max_validators=None, + num_uids=None, + max_uids=None, + burn=None, + difficulty=None, + registration_allowed=None, + pow_registration_allowed=None, + immunity_period=None, + min_difficulty=None, + max_difficulty=None, + min_burn=None, + max_burn=None, + adjustment_alpha=None, + adjustment_interval=None, + target_regs_per_interval=None, + max_regs_per_block=None, + serving_rate_limit=None, + commit_reveal_weights_enabled=None, + commit_reveal_period=None, + liquid_alpha_enabled=None, + alpha_high=None, + alpha_low=None, + bonds_moving_avg=None, + hotkeys=None, + coldkeys=None, + identities=None, + validator_permit=None, + pruning_score=None, + last_update=None, + emission=None, + dividends=None, + incentives=None, + consensus=None, + trust=None, + rank=None, + block_at_registration=None, + alpha_stake=None, + tao_stake=None, + total_stake=None, + tao_dividends_per_hotkey=None, + alpha_dividends_per_hotkey=None, + validators=None, + ) + + logging.console.info("✅ Passed [blue]test_metagraph_info_with_indexes[/blue]") + + +@pytest.mark.asyncio +async def test_metagraph_info_with_indexes_async( + async_subtensor, alice_wallet, bob_wallet +): + """ + Async tests: + - Check MetagraphInfo + - Register Neuron + - Register Subnet + - Check MetagraphInfo is updated + """ + logging.console.info("Testing [blue]test_metagraph_info_with_indexes_async[/blue]") + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True) + + field_indices = [ + SelectiveMetagraphIndex.Name, + SelectiveMetagraphIndex.Active, + SelectiveMetagraphIndex.OwnerHotkey, + SelectiveMetagraphIndex.OwnerColdkey, + SelectiveMetagraphIndex.Axons, + ] + + metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( + netuid=alice_subnet_netuid, field_indices=field_indices + ) + + assert metagraph_info == MetagraphInfo( + netuid=alice_subnet_netuid, + name="omron", + owner_hotkey=alice_wallet.hotkey.ss58_address, + owner_coldkey=alice_wallet.coldkey.ss58_address, + active=(True,), + axons=( + { + "block": 0, + "ip": 0, + "ip_type": 0, + "placeholder1": 0, + "placeholder2": 0, + "port": 0, + "protocol": 0, + "version": 0, + }, + ), + symbol=None, + identity=None, + network_registered_at=None, + block=None, + tempo=None, + last_step=None, + blocks_since_last_step=None, + subnet_emission=None, + alpha_in=None, + alpha_out=None, + tao_in=None, + alpha_out_emission=None, + alpha_in_emission=None, + tao_in_emission=None, + pending_alpha_emission=None, + pending_root_emission=None, + subnet_volume=None, + moving_price=None, + rho=None, + kappa=None, + min_allowed_weights=None, + max_weights_limit=None, + weights_version=None, + weights_rate_limit=None, + activity_cutoff=None, + max_validators=None, + num_uids=None, + max_uids=None, + burn=None, + difficulty=None, + registration_allowed=None, + pow_registration_allowed=None, + immunity_period=None, + min_difficulty=None, + max_difficulty=None, + min_burn=None, + max_burn=None, + adjustment_alpha=None, + adjustment_interval=None, + target_regs_per_interval=None, + max_regs_per_block=None, + serving_rate_limit=None, + commit_reveal_weights_enabled=None, + commit_reveal_period=None, + liquid_alpha_enabled=None, + alpha_high=None, + alpha_low=None, + bonds_moving_avg=None, + hotkeys=None, + coldkeys=None, + identities=None, + validator_permit=None, + pruning_score=None, + last_update=None, + emission=None, + dividends=None, + incentives=None, + consensus=None, + trust=None, + rank=None, + block_at_registration=None, + alpha_stake=None, + tao_stake=None, + total_stake=None, + tao_dividends_per_hotkey=None, + alpha_dividends_per_hotkey=None, + validators=None, + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + assert await async_subtensor.subnets.burned_register( bob_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, @@ -571,7 +1256,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): SelectiveMetagraphIndex.Axons, ] - metagraph_info = subtensor.get_metagraph_info( + metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( netuid=alice_subnet_netuid, field_indices=fields ) @@ -672,6 +1357,10 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): validators=None, ) + logging.console.info( + "✅ Passed [blue]test_metagraph_info_with_indexes_async[/blue]" + ) + def test_blocks(subtensor): """ @@ -680,15 +1369,36 @@ def test_blocks(subtensor): - Get block hash - Wait for block """ + logging.console.info("Testing [blue]test_blocks[/blue]") - block = subtensor.get_current_block() - + block = subtensor.chain.get_current_block() assert block == subtensor.block - block_hash = subtensor.get_block_hash(block) - + block_hash = subtensor.chain.get_block_hash(block) assert re.match("0x[a-z0-9]{64}", block_hash) subtensor.wait_for_block(block + 10) + assert subtensor.chain.get_current_block() == block + 10 + + logging.console.info("✅ Passed [blue]test_blocks[/blue]") + - assert subtensor.get_current_block() == block + 10 +@pytest.mark.asyncio +async def test_blocks_async(subtensor): + """ + Async tests: + - Get current block + - Get block hash + - Wait for block + """ + logging.console.info("Testing [blue]test_blocks_async[/blue]") + + block = subtensor.chain.get_current_block() + assert block == subtensor.block + + block_hash = subtensor.chain.get_block_hash(block) + assert re.match("0x[a-z0-9]{64}", block_hash) + + subtensor.wait_for_block(block + 10) + assert subtensor.chain.get_current_block() == block + 10 + logging.console.info("✅ Passed [blue]test_blocks_async[/blue]") From c4bbca1154cc249c6426e2e940ea6feb9fe4f29c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:02:10 -0700 Subject: [PATCH 34/86] async test for `tests/e2e_tests/test_neuron_certificate.py` --- tests/e2e_tests/test_neuron_certificate.py | 72 +++++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index bd319e27a4..0a1748d8eb 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -15,24 +15,24 @@ async def test_neuron_certificate(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.info("Testing neuron_certificate") + logging.info("Testing [blue]neuron_certificate[/blue]") netuid = 2 # Register root as Alice - the subnet owner and validator - assert subtensor.register_subnet(alice_wallet) + assert subtensor.subnets.register_subnet(alice_wallet) # Verify subnet created successfully - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" # Register Alice as a neuron on the subnet - assert subtensor.burned_register(alice_wallet, netuid), ( + assert subtensor.subnets.burned_register(alice_wallet, netuid), ( "Unable to register Alice as a neuron" ) # Serve Alice's axon with a certificate axon = Axon(wallet=alice_wallet) encoded_certificate = "?FAKE_ALICE_CERT" - subtensor.serve_axon( + subtensor.extrinsics.serve_axon( netuid, axon, certificate=encoded_certificate, @@ -42,14 +42,70 @@ async def test_neuron_certificate(subtensor, alice_wallet): # Verify we are getting the correct certificate assert ( - subtensor.get_neuron_certificate( + subtensor.neurons.get_neuron_certificate( netuid=netuid, hotkey=alice_wallet.hotkey.ss58_address, ) == encoded_certificate ) - all_certs_query = subtensor.get_all_neuron_certificates(netuid=netuid) + all_certs_query = subtensor.neurons.get_all_neuron_certificates(netuid=netuid) assert alice_wallet.hotkey.ss58_address in all_certs_query.keys() assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate - logging.info("✅ Passed test_neuron_certificate") + logging.console.success("✅ Passed [blue]test_neuron_certificate[/blue]") + + +@pytest.mark.asyncio +async def test_neuron_certificate_async(async_subtensor, alice_wallet): + """ + ASync tests the metagraph + + Steps: + 1. Register a subnet through Alice + 2. Serve Alice axon with neuron certificate + 3. Verify neuron certificate can be retrieved + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.info("Testing [blue]neuron_certificate[/blue]") + netuid = 2 + + # Register root as Alice - the subnet owner and validator + assert await async_subtensor.subnets.register_subnet(alice_wallet) + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + # Register Alice as a neuron on the subnet + assert await async_subtensor.subnets.burned_register(alice_wallet, netuid), ( + "Unable to register Alice as a neuron" + ) + + # Serve Alice's axon with a certificate + axon = Axon(wallet=alice_wallet) + encoded_certificate = "?FAKE_ALICE_CERT" + await async_subtensor.extrinsics.serve_axon( + netuid, + axon, + certificate=encoded_certificate, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Verify we are getting the correct certificate + assert ( + await async_subtensor.neurons.get_neuron_certificate( + netuid=netuid, + hotkey=alice_wallet.hotkey.ss58_address, + ) + == encoded_certificate + ) + all_certs_query = await async_subtensor.neurons.get_all_neuron_certificates( + netuid=netuid + ) + assert alice_wallet.hotkey.ss58_address in all_certs_query.keys() + assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate + + logging.console.success("✅ Passed [blue]test_neuron_certificate[/blue]") From 0d356fcea4da38c581e9b4732145a9db0399e38e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:14:21 -0700 Subject: [PATCH 35/86] async test for `tests/e2e_tests/test_reveal_commitments.py` --- tests/e2e_tests/test_reveal_commitments.py | 175 ++++++++++++++++++--- 1 file changed, 152 insertions(+), 23 deletions(-) diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index c4757e24cb..97b60373fd 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -3,12 +3,13 @@ import pytest from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) -@pytest.mark.parametrize("local_chain", [True], indirect=True) -@pytest.mark.asyncio -async def test_set_reveal_commitment(local_chain, subtensor, alice_wallet, bob_wallet): +def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): """ Tests the set/reveal commitments with TLE (time-locked encrypted commitments) mechanism. @@ -26,36 +27,153 @@ async def test_set_reveal_commitment(local_chain, subtensor, alice_wallet, bob_w Note: Actually we can run this tests in fast block mode. For this we need to set `BLOCK_TIME` to 0.25 and replace `False` to `True` in `pytest.mark.parametrize` decorator. """ - BLOCK_TIME = ( - 0.25 if subtensor.is_fast_blocks() else 12.0 - ) # 12 for non-fast-block, 0.25 for fast block - BLOCKS_UNTIL_REVEAL = 10 if subtensor.is_fast_blocks() else 5 + logging.console.info("Testing [blue]test_set_reveal_commitment[/blue]") + + BLOCK_TIME, BLOCKS_UNTIL_REVEAL = ( + (0.25, 10) if subtensor.chain.is_fast_blocks() else (12.0, 5) + ) - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 logging.console.info("Testing Drand encrypted commitments.") # Register subnet as Alice - assert subtensor.register_subnet(alice_wallet, True, True), ( + assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( "Unable to register the subnet" ) assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) # Register Bob's neuron - assert subtensor.burned_register(bob_wallet, alice_subnet_netuid, True, True), ( - "Bob's neuron was not register." + assert subtensor.subnets.burned_register( + bob_wallet, alice_subnet_netuid, True, True + ), "Bob's neuron was not register." + + # Verify subnet 2 created successfully + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + # Set commitment from Alice hotkey + message_alice = f"This is test message with time {time.time()} from Alice." + + response = subtensor.commitments.set_reveal_commitment( + alice_wallet, + alice_subnet_netuid, + message_alice, + BLOCKS_UNTIL_REVEAL, + BLOCK_TIME, + ) + assert response[0] is True + + # Set commitment from Bob's hotkey + message_bob = f"This is test message with time {time.time()} from Bob." + + response = subtensor.commitments.set_reveal_commitment( + bob_wallet, + alice_subnet_netuid, + message_bob, + BLOCKS_UNTIL_REVEAL, + block_time=BLOCK_TIME, + ) + assert response[0] is True + + target_reveal_round = response[1] + + # Sometimes the chain doesn't update the repository right away and the commit doesn't appear in the expected + # `last_drand_round`. In this case need to wait a bit. + print(f"Waiting for reveal round {target_reveal_round}") + chain_offset = 1 if subtensor.chain.is_fast_blocks() else 24 + + last_drand_round = -1 + while last_drand_round <= target_reveal_round + chain_offset: + # wait one drand period (3 sec) + last_drand_round = subtensor.chain.last_drand_round() + print(f"Current last reveled drand round {last_drand_round}") + time.sleep(3) + + actual_all = subtensor.commitments.get_all_revealed_commitments(alice_subnet_netuid) + + alice_result = actual_all.get(alice_wallet.hotkey.ss58_address) + assert alice_result is not None, "Alice's commitment was not received." + + bob_result = actual_all.get(bob_wallet.hotkey.ss58_address) + assert bob_result is not None, "Bob's commitment was not received." + + alice_actual_block, alice_actual_message = alice_result[0] + bob_actual_block, bob_actual_message = bob_result[0] + + # We do not check the release block because it is a dynamic number. It depends on the load of the chain, the number + # of commits in the chain and the computing power. + assert message_alice == alice_actual_message + assert message_bob == bob_actual_message + + # Assertions for get_revealed_commitment (based of hotkey) + actual_alice_block, actual_alice_message = ( + subtensor.commitments.get_revealed_commitment(alice_subnet_netuid, 0)[0] + ) + actual_bob_block, actual_bob_message = ( + subtensor.commitments.get_revealed_commitment(alice_subnet_netuid, 1)[0] + ) + + assert message_alice == actual_alice_message + assert message_bob == actual_bob_message + + logging.console.success("✅ Passed [blue]test_set_reveal_commitment[/blue]") + + +@pytest.mark.asyncio +async def test_set_reveal_commitment(async_subtensor, alice_wallet, bob_wallet): + """ + Tests the set/reveal commitments with TLE (time-locked encrypted commitments) mechanism. + + Steps: + 1. Register a subnet through Alice + 2. Register Bob's neuron and add stake + 3. Set commitment from Alice hotkey + 4. Set commitment from Bob hotkey + 5. Wait until commitment is revealed. + 5. Verify commitment is revealed by Alice and Bob and available via mutual call. + 6. Verify commitment is revealed by Alice and Bob and available via separate calls. + Raises: + AssertionError: If any of the checks or verifications fail + + Note: Actually we can run this tests in fast block mode. For this we need to set `BLOCK_TIME` to 0.25 and replace + `False` to `True` in `pytest.mark.parametrize` decorator. + """ + logging.console.info("Testing [blue]test_set_reveal_commitment[/blue]") + + BLOCK_TIME, BLOCKS_UNTIL_REVEAL = ( + (0.25, 10) if await async_subtensor.chain.is_fast_blocks() else (12.0, 5) + ) + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + logging.console.info("Testing Drand encrypted commitments.") + + # Register subnet as Alice + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True), ( + "Unable to register the subnet" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid ) + # Register Bob's neuron + assert await async_subtensor.subnets.burned_register( + bob_wallet, alice_subnet_netuid, True, True + ), "Bob's neuron was not register." + # Verify subnet 2 created successfully - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) # Set commitment from Alice hotkey message_alice = f"This is test message with time {time.time()} from Alice." - response = subtensor.set_reveal_commitment( + response = await async_subtensor.commitments.set_reveal_commitment( alice_wallet, alice_subnet_netuid, message_alice, @@ -67,7 +185,7 @@ async def test_set_reveal_commitment(local_chain, subtensor, alice_wallet, bob_w # Set commitment from Bob's hotkey message_bob = f"This is test message with time {time.time()} from Bob." - response = subtensor.set_reveal_commitment( + response = await async_subtensor.commitments.set_reveal_commitment( bob_wallet, alice_subnet_netuid, message_bob, @@ -81,13 +199,18 @@ async def test_set_reveal_commitment(local_chain, subtensor, alice_wallet, bob_w # Sometimes the chain doesn't update the repository right away and the commit doesn't appear in the expected # `last_drand_round`. In this case need to wait a bit. print(f"Waiting for reveal round {target_reveal_round}") - chain_offset = 1 if subtensor.is_fast_blocks() else 24 - while subtensor.last_drand_round() <= target_reveal_round + chain_offset: + chain_offset = 1 if await async_subtensor.chain.is_fast_blocks() else 24 + + last_drand_round = -1 + while last_drand_round <= target_reveal_round + chain_offset: # wait one drand period (3 sec) - print(f"Current last reveled drand round {subtensor.last_drand_round()}") + last_drand_round = await async_subtensor.chain.last_drand_round() + print(f"Current last reveled drand round {last_drand_round}") time.sleep(3) - actual_all = subtensor.get_all_revealed_commitments(alice_subnet_netuid) + actual_all = await async_subtensor.commitments.get_all_revealed_commitments( + alice_subnet_netuid + ) alice_result = actual_all.get(alice_wallet.hotkey.ss58_address) assert alice_result is not None, "Alice's commitment was not received." @@ -104,12 +227,18 @@ async def test_set_reveal_commitment(local_chain, subtensor, alice_wallet, bob_w assert message_bob == bob_actual_message # Assertions for get_revealed_commitment (based of hotkey) - actual_alice_block, actual_alice_message = subtensor.get_revealed_commitment( - alice_subnet_netuid, 0 + actual_alice_block, actual_alice_message = ( + await async_subtensor.commitments.get_revealed_commitment( + alice_subnet_netuid, 0 + ) )[0] - actual_bob_block, actual_bob_message = subtensor.get_revealed_commitment( - alice_subnet_netuid, 1 + actual_bob_block, actual_bob_message = ( + await async_subtensor.commitments.get_revealed_commitment( + alice_subnet_netuid, 1 + ) )[0] assert message_alice == actual_alice_message assert message_bob == actual_bob_message + + logging.console.success("✅ Passed [blue]test_set_reveal_commitment[/blue]") From 09595258ed93751e2512f9f19caf6a045989cc6d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:32:00 -0700 Subject: [PATCH 36/86] async test for `tests/e2e_tests/test_root_set_weights.py` --- tests/e2e_tests/test_root_set_weights.py | 193 +++++++++++++++++++---- 1 file changed, 158 insertions(+), 35 deletions(-) diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index bec739148e..7ce08d9f47 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -3,11 +3,17 @@ import pytest from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( + async_wait_epoch, + async_sudo_set_hyperparameter_values, wait_epoch, sudo_set_hyperparameter_values, ) -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) FAST_BLOCKS_SPEEDUP_FACTOR = 5 @@ -32,13 +38,7 @@ @pytest.mark.asyncio -async def test_root_reg_hyperparams( - local_chain, - subtensor, - templates, - alice_wallet, - bob_wallet, -): +async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wallet): """ Test root weights and hyperparameters in the Subtensor network. @@ -60,37 +60,33 @@ async def test_root_reg_hyperparams( AssertionError: If any of the checks or verifications fail. """ - print("Testing root register, weights, and hyperparams") - netuid = subtensor.get_total_subnets() # 2 + logging.console.info("Testing root register, weights, and hyperparams") + netuid = subtensor.subnets.get_total_subnets() # 2 # Default immunity period and tempo set through the subtensor side default_immunity_period = 5000 - default_tempo = 10 if subtensor.is_fast_blocks() else 360 - - # 0.2 for root network, 0.8 for sn 1 - # Corresponding to [0.2, 0.8] - weights = [16384, 65535] + default_tempo = 10 if subtensor.chain.is_fast_blocks() else 360 # Register Alice in root network (0) - assert subtensor.root_register(alice_wallet) + assert subtensor.extrinsics.root_register(alice_wallet) # Assert Alice is successfully registered to root - alice_root_neuron = subtensor.neurons(netuid=0)[0] + alice_root_neuron = subtensor.neurons.neurons(netuid=0)[0] assert alice_root_neuron.coldkey == alice_wallet.coldkeypub.ss58_address assert alice_root_neuron.hotkey == alice_wallet.hotkey.ss58_address # Create netuid = 2 - assert subtensor.register_subnet(alice_wallet) + assert subtensor.subnets.register_subnet(alice_wallet) assert wait_to_start_call( subtensor=subtensor, subnet_owner_wallet=alice_wallet, netuid=netuid ) # Ensure correct immunity period and tempo is being fetched - assert subtensor.immunity_period(netuid=netuid) == default_immunity_period - assert subtensor.tempo(netuid=netuid) == default_tempo + assert subtensor.subnets.immunity_period(netuid=netuid) == default_immunity_period + assert subtensor.subnets.tempo(netuid=netuid) == default_tempo - assert subtensor.add_stake( + assert subtensor.staking.add_stake( wallet=bob_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid, @@ -104,43 +100,170 @@ async def test_root_reg_hyperparams( await asyncio.sleep(5) # Wait a bit for chain to process data # Fetch uid against Alice's hotkey on sn 2 (it will be 0 as she is the only registered neuron) - alice_uid_sn_2 = subtensor.get_uid_for_hotkey_on_subnet( + alice_uid_sn_2 = subtensor.subnets.get_uid_for_hotkey_on_subnet( alice_wallet.hotkey.ss58_address, netuid ) # Fetch the block since last update for the neuron - block_since_update = subtensor.blocks_since_last_update( + block_since_update = subtensor.subnets.blocks_since_last_update( netuid=netuid, uid=alice_uid_sn_2 ) assert block_since_update is not None # Verify subnet created successfully - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" # Use subnetwork_n hyperparam to check sn creation - assert subtensor.subnetwork_n(netuid) == 1 # TODO? - assert subtensor.subnetwork_n(netuid + 1) is None + assert subtensor.subnets.subnetwork_n(netuid) == 1 # TODO? + assert subtensor.subnets.subnetwork_n(netuid + 1) is None # Ensure correct hyperparams are being fetched regarding weights - assert subtensor.min_allowed_weights(netuid) is not None - assert subtensor.max_weight_limit(netuid) is not None - assert subtensor.weights_rate_limit(netuid) is not None + assert subtensor.subnets.min_allowed_weights(netuid) is not None + assert subtensor.subnets.max_weight_limit(netuid) is not None + assert subtensor.subnets.weights_rate_limit(netuid) is not None # Wait until next epoch so we can set root weights await wait_epoch(subtensor, netuid) # Change hyperparams so we can execute pow_register assert sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_difficulty", call_params={"netuid": netuid, "difficulty": "1_000_000"}, return_error_message=True, ) assert sudo_set_hyperparameter_values( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_network_pow_registration_allowed", + call_params={"netuid": netuid, "registration_allowed": True}, + return_error_message=True, + ) + + # TODO: Implement + # This registers neuron using pow but it doesn't work on fast-blocks - we get stale pow + # pow_registration = subtensor.register(bob_wallet, netuid=1) + + # Fetch neuron lite for sn one and assert Alice participation + sn_one_neurons = subtensor.neurons.neurons_lite(netuid=netuid) + assert ( + sn_one_neurons[alice_uid_sn_2].coldkey == alice_wallet.coldkeypub.ss58_address + ) + assert sn_one_neurons[alice_uid_sn_2].hotkey == alice_wallet.hotkey.ss58_address + assert sn_one_neurons[alice_uid_sn_2].validator_permit is True + + logging.console.success("✅ Passed root tests") + + +@pytest.mark.asyncio +async def test_root_reg_hyperparams_async( + async_subtensor, templates, alice_wallet, bob_wallet +): + """ + Async test root weights and hyperparameters in the Subtensor network. + + Steps: + 1. Register Alice in the root network (netuid=0). + 2. Create a new subnet (netuid=1) and register Alice on this subnet using burned registration. + 3. Verify that the subnet's `immunity_period` and `tempo` match the default values. + 4. Run Alice as a validator in the background. + 5. Fetch Alice's UID on the subnet and record the blocks since her last update. + 6. Verify that the subnet was created successfully by checking `subnetwork_n`. + 7. Verify hyperparameters related to weights: `min_allowed_weights`, `max_weight_limit`, and `weights_rate_limit`. + 8. Wait until the next epoch and set root weights for netuids 0 and 1. + 9. Verify that the weights are correctly set on the chain. + 10. Adjust hyperparameters to allow proof-of-work (PoW) registration. + 11. Verify that the `blocks_since_last_update` has incremented. + 12. Fetch neurons using `neurons_lite` for the subnet and verify Alice's participation. + + Raises: + AssertionError: If any of the checks or verifications fail. + """ + + logging.console.info("Testing root register, weights, and hyperparams") + netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Default immunity period and tempo set through the subtensor side + default_immunity_period = 5000 + default_tempo = 10 if await async_subtensor.chain.is_fast_blocks() else 360 + + # Register Alice in root network (0) + assert await async_subtensor.extrinsics.root_register(alice_wallet) + + # Assert Alice is successfully registered to root + alice_root_neuron = (await async_subtensor.neurons.neurons(netuid=0))[0] + assert alice_root_neuron.coldkey == alice_wallet.coldkeypub.ss58_address + assert alice_root_neuron.hotkey == alice_wallet.hotkey.ss58_address + + # Create netuid = 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet) + + assert await async_wait_to_start_call( + subtensor=async_subtensor, subnet_owner_wallet=alice_wallet, netuid=netuid + ) + + # Ensure correct immunity period and tempo is being fetched + assert ( + await async_subtensor.subnets.immunity_period(netuid=netuid) + == default_immunity_period + ) + assert await async_subtensor.subnets.tempo(netuid=netuid) == default_tempo + + assert await async_subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=netuid, + amount=Balance.from_tao(1), + wait_for_inclusion=True, + wait_for_finalization=True, + period=16, + ), "Unable to stake from Bob to Alice" + + async with templates.validator(alice_wallet, netuid): + await asyncio.sleep(5) # Wait a bit for chain to process data + + # Fetch uid against Alice's hotkey on sn 2 (it will be 0 as she is the only registered neuron) + alice_uid_sn_2 = await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( + alice_wallet.hotkey.ss58_address, netuid + ) + + # Fetch the block since last update for the neuron + block_since_update = await async_subtensor.subnets.blocks_since_last_update( + netuid=netuid, uid=alice_uid_sn_2 + ) + assert block_since_update is not None + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + # Use subnetwork_n hyperparam to check sn creation + assert await async_subtensor.subnets.subnetwork_n(netuid) == 1 # TODO? + assert await async_subtensor.subnets.subnetwork_n(netuid + 1) is None + + # Ensure correct hyperparams are being fetched regarding weights + assert await async_subtensor.subnets.min_allowed_weights(netuid) is not None + assert await async_subtensor.subnets.max_weight_limit(netuid) is not None + assert await async_subtensor.subnets.weights_rate_limit(netuid) is not None + + # Wait until next epoch so we can set root weights + await async_wait_epoch(async_subtensor, netuid) + + # Change hyperparams so we can execute pow_register + assert await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_difficulty", + call_params={"netuid": netuid, "difficulty": "1_000_000"}, + return_error_message=True, + ) + + assert await async_sudo_set_hyperparameter_values( + substrate=async_subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_network_pow_registration_allowed", call_params={"netuid": netuid, "registration_allowed": True}, return_error_message=True, @@ -151,11 +274,11 @@ async def test_root_reg_hyperparams( # pow_registration = subtensor.register(bob_wallet, netuid=1) # Fetch neuron lite for sn one and assert Alice participation - sn_one_neurons = subtensor.neurons_lite(netuid=netuid) + sn_one_neurons = await async_subtensor.neurons.neurons_lite(netuid=netuid) assert ( sn_one_neurons[alice_uid_sn_2].coldkey == alice_wallet.coldkeypub.ss58_address ) assert sn_one_neurons[alice_uid_sn_2].hotkey == alice_wallet.hotkey.ss58_address assert sn_one_neurons[alice_uid_sn_2].validator_permit is True - print("✅ Passed root tests") + logging.console.success("✅ Passed root tests") From ac6fa823a1f30872f1c05855f7bd75c6f26c9928 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:53:22 -0700 Subject: [PATCH 37/86] async test for `tests/e2e_tests/test_set_subnet_identity_extrinsic.py` --- .../test_set_subnet_identity_extrinsic.py | 157 ++++++++++++++++-- 1 file changed, 140 insertions(+), 17 deletions(-) diff --git a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py index 5622322818..de52ad4550 100644 --- a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py +++ b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py @@ -4,22 +4,21 @@ from bittensor.utils.btlogging import logging -@pytest.mark.asyncio -async def test_set_subnet_identity_extrinsic_happy_pass(subtensor, alice_wallet): +def test_set_subnet_identity_extrinsic_happy_pass(subtensor, alice_wallet): logging.console.info( - "[magenta]Testing `set_subnet_identity_extrinsic` with success result.[/magenta]" + "Testing [blue]test_set_subnet_identity_extrinsic_happy_pass[/blue]" ) - netuid = subtensor.get_total_subnets() # 2 + netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.register_subnet(alice_wallet), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" # Verify subnet created successfully - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" # Make sure subnet_identity is empty - assert subtensor.subnet(netuid).subnet_identity is None, ( + assert subtensor.subnets.subnet(netuid).subnet_identity is None, ( "Subnet identity should be None before set" ) @@ -37,7 +36,7 @@ async def test_set_subnet_identity_extrinsic_happy_pass(subtensor, alice_wallet) # Set SubnetIdentity to subnet assert ( - subtensor.set_subnet_identity( + subtensor.subnets.set_subnet_identity( wallet=alice_wallet, netuid=netuid, subnet_identity=subnet_identity, @@ -46,13 +45,69 @@ async def test_set_subnet_identity_extrinsic_happy_pass(subtensor, alice_wallet) ), "Set subnet identity failed" # Check SubnetIdentity of the subnet - assert subtensor.subnet(netuid).subnet_identity == subnet_identity + assert subtensor.subnets.subnet(netuid).subnet_identity == subnet_identity + + logging.console.success( + "✅ Passed [blue]test_set_subnet_identity_extrinsic_happy_pass[/blue]" + ) @pytest.mark.asyncio -async def test_set_subnet_identity_extrinsic_failed( - subtensor, alice_wallet, bob_wallet +async def test_set_subnet_identity_extrinsic_happy_pass_async( + async_subtensor, alice_wallet ): + logging.console.info( + "Testing [blue]test_set_subnet_identity_extrinsic_happy_pass_async[/blue]" + ) + + netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Register a subnet, netuid 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Subnet wasn't created" + ) + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + # Make sure subnet_identity is empty + assert (await async_subtensor.subnets.subnet(netuid)).subnet_identity is None, ( + "Subnet identity should be None before set" + ) + + # Prepare SubnetIdentity for subnet + subnet_identity = SubnetIdentity( + subnet_name="e2e test subnet", + github_repo="e2e test repo", + subnet_contact="e2e test contact", + subnet_url="e2e test url", + logo_url="e2e test logo url", + discord="e2e test discord", + description="e2e test description", + additional="e2e test additional", + ) + + # Set SubnetIdentity to subnet + assert ( + await async_subtensor.subnets.set_subnet_identity( + wallet=alice_wallet, + netuid=netuid, + subnet_identity=subnet_identity, + ) + )[0] is True, "Set subnet identity failed" + + # Check SubnetIdentity of the subnet + assert ( + await async_subtensor.subnets.subnet(netuid) + ).subnet_identity == subnet_identity + logging.console.success( + "✅ Passed [blue]test_set_subnet_identity_extrinsic_happy_pass_async[/blue]" + ) + + +def test_set_subnet_identity_extrinsic_failed(subtensor, alice_wallet, bob_wallet): """ Test case for verifying the behavior of the `set_subnet_identity_extrinsic` function in the scenario where the result of the function is expected to fail. It ensures proper handling @@ -67,19 +122,19 @@ async def test_set_subnet_identity_extrinsic_failed( @pytest.mark.asyncio: Marks this test as an asynchronous test. """ logging.console.info( - "[magenta]Testing `set_subnet_identity_extrinsic` with failed result.[/magenta]" + "Testing [blue]test_set_subnet_identity_extrinsic_failed[/blue]" ) - netuid = subtensor.get_total_subnets() # 2 + netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.register_subnet(alice_wallet), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" # Verify subnet created successfully - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" # Make sure subnet_identity is empty - assert subtensor.subnet(netuid).subnet_identity is None, ( + assert subtensor.subnets.subnet(netuid).subnet_identity is None, ( "Subnet identity should be None before set" ) @@ -97,10 +152,78 @@ async def test_set_subnet_identity_extrinsic_failed( # Set SubnetIdentity to subnet with wrong wallet assert ( - subtensor.set_subnet_identity( + subtensor.subnets.set_subnet_identity( wallet=bob_wallet, netuid=netuid, subnet_identity=subnet_identity, )[0] is False ), "Set subnet identity failed" + + logging.console.success( + "✅ Passed [blue]test_set_subnet_identity_extrinsic_failed[/blue]" + ) + + +@pytest.mark.asyncio +async def test_set_subnet_identity_extrinsic_failed_async( + async_subtensor, alice_wallet, bob_wallet +): + """ + Async test case for verifying the behavior of the `set_subnet_identity_extrinsic` function in the + scenario where the result of the function is expected to fail. It ensures proper handling + and validation when attempting to set the subnet identity under specific conditions. + + Args: + subtensor: The instance of the subtensor class under test. + alice_wallet: A mock or test wallet associated with Alice, used for creating a subnet. + bob_wallet: A mock or test wallet associated with Bob, used for setting the subnet identity. + + Decorators: + @pytest.mark.asyncio: Marks this test as an asynchronous test. + """ + logging.console.info( + "Testing [blue]test_set_subnet_identity_extrinsic_failed[/blue]" + ) + + netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Register a subnet, netuid 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Subnet wasn't created" + ) + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + # Make sure subnet_identity is empty + assert (await async_subtensor.subnets.subnet(netuid)).subnet_identity is None, ( + "Subnet identity should be None before set" + ) + + # Prepare SubnetIdentity for subnet + subnet_identity = SubnetIdentity( + subnet_name="e2e test subnet", + github_repo="e2e test repo", + subnet_contact="e2e test contact", + subnet_url="e2e test url", + logo_url="e2e test logo url", + discord="e2e test discord", + description="e2e test description", + additional="e2e test additional", + ) + + # Set SubnetIdentity to subnet with wrong wallet + assert ( + await async_subtensor.subnets.set_subnet_identity( + wallet=bob_wallet, + netuid=netuid, + subnet_identity=subnet_identity, + ) + )[0] is False, "Set subnet identity failed" + + logging.console.success( + "✅ Passed [blue]test_set_subnet_identity_extrinsic_failed[/blue]" + ) From 411ec3a94580146ce86ae51accc2dbd0a4b849e1 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 14:37:01 -0700 Subject: [PATCH 38/86] async test for `tests/e2e_tests/test_set_weights.py` --- tests/e2e_tests/test_set_weights.py | 314 ++++++++++++++++++++++++---- 1 file changed, 274 insertions(+), 40 deletions(-) diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index ce8e628bb6..0e153b804e 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -1,19 +1,24 @@ import numpy as np import pytest import retry - +import time from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_hyperparameter_bool, + async_sudo_set_admin_utils, sudo_set_hyperparameter_bool, sudo_set_admin_utils, execute_and_wait_for_next_nonce, ) +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) -@pytest.mark.asyncio -async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet): +def test_set_weights_uses_next_nonce(subtensor, alice_wallet): """ Tests that setting weights doesn't re-use a nonce in the transaction pool. @@ -27,44 +32,43 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing [blue]test_set_weights_uses_next_nonce[/blue]") netuids = [2, 3] subnet_tempo = 50 - BLOCK_TIME = 0.25 # 12 for non-fast-block, 0.25 for fast block - - print("Testing test_set_weights_uses_next_nonce") # Lower the network registration rate limit and cost sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_network_rate_limit", call_params={"rate_limit": "0"}, # No limit ) # Set lock reduction interval sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_lock_reduction_interval", call_params={"interval": "1"}, # 1 block # reduce lock every block ) - # Try to register the subnets - for _ in netuids: - assert subtensor.register_subnet( - alice_wallet, + for netuid in netuids: + # Register the subnets + assert subtensor.subnets.register_subnet( + wallet=alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ), "Unable to register the subnet" - # Verify all subnets created successfully - for netuid in netuids: - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + # Verify all subnets created successfully + assert subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) - # weights sensitive to epoch changes + # Weights sensitive to epoch changes assert sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={ "netuid": netuid, @@ -72,40 +76,44 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) }, ) - # make sure 2 epochs are passed + assert wait_to_start_call(subtensor, alice_wallet, netuid) + + # Make sure 2 epochs are passed subtensor.wait_for_block(subnet_tempo * 2 + 1) # Stake to become to top neuron after the first epoch for netuid in netuids: - subtensor.add_stake( - alice_wallet, - alice_wallet.hotkey.ss58_address, - netuid, - Balance.from_tao(10_000), + assert subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=netuid, + amount=Balance.from_tao(10_000), + wait_for_inclusion=True, + wait_for_finalization=True, ) # Set weight hyperparameters per subnet for netuid in netuids: assert sudo_set_hyperparameter_bool( - local_chain, - alice_wallet, - "sudo_set_commit_reveal_weights_enabled", - False, - netuid, + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=False, + netuid=netuid, ), "Unable to enable commit reveal on the subnet" - assert not subtensor.commit_reveal_enabled( + assert not subtensor.subnets.commit_reveal_enabled( netuid, ), "Failed to enable commit/reveal" - assert subtensor.weights_rate_limit(netuid=netuid) > 0, ( + assert subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( "Weights rate limit is below 0" ) # Lower set weights rate limit status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, ) @@ -114,10 +122,13 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) assert status is True assert ( - subtensor.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0 + subtensor.subnets.get_subnet_hyperparameters( + netuid=netuid + ).weights_rate_limit + == 0 ), "Failed to set weights_rate_limit" - assert subtensor.get_hyperparameter("WeightsSetRateLimit", netuid) == 0 - assert subtensor.weights_rate_limit(netuid=netuid) == 0 + assert subtensor.subnets.get_hyperparameter("WeightsSetRateLimit", netuid) == 0 + assert subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 # Weights values uids = np.array([0], dtype=np.int64) @@ -135,7 +146,7 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) @retry.retry(exceptions=Exception, tries=3, delay=1) @execute_and_wait_for_next_nonce(subtensor=subtensor, wallet=alice_wallet) def set_weights(netuid_): - success, message = subtensor.set_weights( + success, message = subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=netuid_, uids=weight_uids, @@ -157,7 +168,226 @@ def set_weights(netuid_): for netuid in netuids: # Query the Weights storage map for all three subnets - query = subtensor.query_module( + query = subtensor.queries.query_module( + module="SubtensorModule", + name="Weights", + params=[netuid, 0], # Alice should be the only UID + ) + + weights = query.value + logging.console.info(f"Weights for subnet {netuid}: {weights}") + + assert weights is not None, f"Weights not found for subnet {netuid}" + assert weights == list(zip(weight_uids, weight_vals)), ( + f"Weights do not match for subnet {netuid}" + ) + + logging.console.info("✅ Passed [blue]test_set_weights_uses_next_nonce[/blue]") + + +@pytest.mark.asyncio +async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): + """ + Async tests that setting weights doesn't re-use a nonce in the transaction pool. + + Steps: + 1. Register three subnets through Alice + 2. Register Alice's neuron on each subnet and add stake + 3. Verify Alice has a vpermit on each subnet + 4. Lower the set weights rate limit on each subnet + 5. Set weights on each subnet + 6. Assert that all the set weights succeeded + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing [blue]test_set_weights_uses_next_nonce_async[/blue]") + + netuids = [2, 3] + subnet_tempo = 50 + + # Lower the network registration rate limit and cost + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_network_rate_limit", + call_params={"rate_limit": "0"}, # No limit + ) + # Set lock reduction interval + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_lock_reduction_interval", + call_params={"interval": "1"}, # 1 block # reduce lock every block + ) + + for netuid in netuids: + # Register the subnets + assert await async_subtensor.subnets.register_subnet( + wallet=alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ), "Unable to register the subnet" + + # Verify all subnets created successfully + assert await async_subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) + + # Weights sensitive to epoch changes + assert await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": netuid, + "tempo": subnet_tempo, + }, + ) + + assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) + + # Make sure 2 epochs are passed + await async_subtensor.wait_for_block(subnet_tempo * 2 + 1) + + # Stake to become to top neuron after the first epoch + for netuid in netuids: + assert await async_subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=netuid, + amount=Balance.from_tao(10_000), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Set weight hyperparameters per subnet + for netuid in netuids: + assert await async_sudo_set_hyperparameter_bool( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=False, + netuid=netuid, + ), "Unable to enable commit reveal on the subnet" + + assert not await async_subtensor.subnets.commit_reveal_enabled( + netuid, + ), "Failed to enable commit/reveal" + + assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( + "Weights rate limit is below 0" + ) + + # Lower set weights rate limit + status, error = await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, + ) + + assert error is None + assert status is True + + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) + ).weights_rate_limit == 0, "Failed to set weights_rate_limit" + assert ( + await async_subtensor.subnets.get_hyperparameter( + "WeightsSetRateLimit", netuid + ) + == 0 + ) + assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 + + # Weights values + uids = np.array([0], dtype=np.int64) + weights = np.array([0.5], dtype=np.float32) + weight_uids, weight_vals = convert_weights_and_uids_for_emit( + uids=uids, weights=weights + ) + + logging.console.info( + f"[orange]Nonce before first set_weights: " + f"{await async_subtensor.substrate.get_account_nonce(alice_wallet.hotkey.ss58_address)}[/orange]" + ) + + # # 3 time doing call if nonce wasn't updated, then raise error + # @retry.retry(exceptions=Exception, tries=3, delay=1) + # @execute_and_wait_for_next_nonce(subtensor=async_subtensor, wallet=alice_wallet) + # def set_weights(netuid_): + # success, message = subtensor.extrinsics.set_weights( + # wallet=alice_wallet, + # netuid=netuid_, + # uids=weight_uids, + # weights=weight_vals, + # wait_for_inclusion=True, + # wait_for_finalization=False, + # period=subnet_tempo, + # ) + # assert success is True, message + + async def set_weights(netuid_): + """ + To avoid adding asynchronous retrieval to dependencies, we implement a retrieval behavior with asynchronous + behavior. + """ + + async def set_weights_(): + success_, message_ = await async_subtensor.extrinsics.set_weights( + wallet=alice_wallet, + netuid=netuid_, + uids=weight_uids, + weights=weight_vals, + wait_for_inclusion=True, + wait_for_finalization=False, + period=subnet_tempo, + ) + assert success_ is True, message_ + + max_retries = 3 + timeout = 60.0 + sleep = 0.25 if async_subtensor.chain.is_fast_blocks() else 12.0 + + for attempt in range(1, max_retries + 1): + try: + start_nonce = await async_subtensor.substrate.get_account_nonce( + alice_wallet.hotkey.ss58_address + ) + + result = await set_weights_() + + start = time.time() + while (time.time() - start) < timeout: + current_nonce = await async_subtensor.substrate.get_account_nonce( + alice_wallet.hotkey.ss58_address + ) + + if current_nonce != start_nonce: + logging.console.info( + f"✅ Nonce changed from {start_nonce} to {current_nonce}" + ) + return result + logging.console.info( + f"⏳ Waiting for nonce increment. Current: {current_nonce}" + ) + time.sleep(sleep) + except Exception as e: + raise e + raise Exception(f"Failed to commit weights after {max_retries} attempts.") + + # Set weights for each subnet + for netuid in netuids: + await set_weights(netuid) + + logging.console.info( + f"[orange]Nonce after second set_weights: " + f"{await async_subtensor.substrate.get_account_nonce(alice_wallet.hotkey.ss58_address)}[/orange]" + ) + + for netuid in netuids: + # Query the Weights storage map for all three subnets + query = await async_subtensor.queries.query_module( module="SubtensorModule", name="Weights", params=[netuid, 0], # Alice should be the only UID @@ -170,3 +400,7 @@ def set_weights(netuid_): assert weights == list(zip(weight_uids, weight_vals)), ( f"Weights do not match for subnet {netuid}" ) + + logging.console.info( + "✅ Passed [blue]test_set_weights_uses_next_nonce_async[/blue]" + ) From 5e5deaa0c2fce62bdc0c16c3a52635917b008ec3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 14:52:45 -0700 Subject: [PATCH 39/86] async test for `tests/e2e_tests/test_stake_fee.py` --- tests/e2e_tests/test_stake_fee.py | 164 ++++++++++++++++++++++++++++-- 1 file changed, 153 insertions(+), 11 deletions(-) diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 049ee3cbe7..8d26ecbed2 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -1,10 +1,144 @@ import pytest -from bittensor import Balance +from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging + + +def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): + """ + Tests the stake fee calculation mechanism for various staking operations + + Steps: + 1. Register a subnet through Alice + 2. Test stake fees for: + - Adding new stake + - Removing stake + - Moving stake between hotkeys/subnets/coldkeys + """ + logging.console.info("Testing [blue]test_stake_fee_api[/blue]") + + netuid = 2 + root_netuid = 0 + stake_amount = Balance.from_tao(100) # 100 TAO + min_stake_fee = Balance.from_tao(0.050354772) + + # Register subnet as Alice + assert subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) + assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" + + # Test add_stake fee + stake_fee_0 = subtensor.staking.get_stake_add_fee( + amount=stake_amount, + netuid=netuid, + coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + ) + assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object." + assert stake_fee_0 == min_stake_fee, ( + "Stake fee should be equal the minimum stake fee." + ) + + # Test unstake fee + unstake_fee_root = subtensor.staking.get_unstake_fee( + amount=stake_amount, + netuid=root_netuid, + coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + ) + assert isinstance(unstake_fee_root, Balance), ( + "Stake fee should be a Balance object." + ) + assert unstake_fee_root == min_stake_fee, ( + "Root unstake fee should be equal the minimum stake fee." + ) + + # Test various stake movement scenarios + movement_scenarios = [ + # Move from root to non-root + { + "origin_netuid": root_netuid, + "origin_hotkey": alice_wallet.hotkey.ss58_address, + "origin_coldkey": alice_wallet.coldkeypub.ss58_address, + "dest_netuid": netuid, + "dest_hotkey": alice_wallet.hotkey.ss58_address, + "dest_coldkey": alice_wallet.coldkeypub.ss58_address, + "stake_fee": min_stake_fee, + }, + # Move between hotkeys on root + { + "origin_netuid": root_netuid, + "origin_hotkey": alice_wallet.hotkey.ss58_address, + "origin_coldkey": alice_wallet.coldkeypub.ss58_address, + "dest_netuid": root_netuid, + "dest_hotkey": bob_wallet.hotkey.ss58_address, + "dest_coldkey": alice_wallet.coldkeypub.ss58_address, + "stake_fee": 0, + }, + # Move between coldkeys on root + { + "origin_netuid": root_netuid, + "origin_hotkey": bob_wallet.hotkey.ss58_address, + "origin_coldkey": alice_wallet.coldkeypub.ss58_address, + "dest_netuid": root_netuid, + "dest_hotkey": bob_wallet.hotkey.ss58_address, + "dest_coldkey": bob_wallet.coldkeypub.ss58_address, + "stake_fee": 0, + }, + # Move between coldkeys on non-root + { + "origin_netuid": netuid, + "origin_hotkey": bob_wallet.hotkey.ss58_address, + "origin_coldkey": alice_wallet.coldkeypub.ss58_address, + "dest_netuid": netuid, + "dest_hotkey": bob_wallet.hotkey.ss58_address, + "dest_coldkey": bob_wallet.coldkeypub.ss58_address, + "stake_fee": min_stake_fee, + }, + ] + + for scenario in movement_scenarios: + stake_fee = subtensor.staking.get_stake_movement_fee( + amount=stake_amount, + origin_netuid=scenario["origin_netuid"], + origin_hotkey_ss58=scenario["origin_hotkey"], + origin_coldkey_ss58=scenario["origin_coldkey"], + destination_netuid=scenario["dest_netuid"], + destination_hotkey_ss58=scenario["dest_hotkey"], + destination_coldkey_ss58=scenario["dest_coldkey"], + ) + assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object" + assert stake_fee >= scenario["stake_fee"], ( + "Stake fee should be greater than the minimum stake fee" + ) + + # Test cross-subnet movement + netuid2 = 3 + assert subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the second subnet" + ) + assert subtensor.subnets.subnet_exists(netuid2), ( + "Second subnet wasn't created successfully" + ) + + stake_fee = subtensor.staking.get_stake_movement_fee( + amount=stake_amount, + origin_netuid=netuid, + origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, + origin_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + destination_netuid=netuid2, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_coldkey_ss58=alice_wallet.coldkeypub.ss58_address, + ) + assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object" + assert stake_fee >= min_stake_fee, ( + "Stake fee should be greater than the minimum stake fee" + ) + logging.console.success("✅ Passed [blue]test_stake_fee_api[/blue]") -@pytest.mark.parametrize("local_chain", [False], indirect=True) @pytest.mark.asyncio -async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): +async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): """ Tests the stake fee calculation mechanism for various staking operations @@ -15,6 +149,7 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): - Removing stake - Moving stake between hotkeys/subnets/coldkeys """ + logging.console.info("Testing [blue]test_stake_fee_api_async[/blue]") netuid = 2 root_netuid = 0 @@ -22,11 +157,15 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): min_stake_fee = Balance.from_tao(0.050354772) # Register subnet as Alice - assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" - assert subtensor.subnet_exists(netuid), "Subnet wasn't created successfully" + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) + assert await async_subtensor.subnets.subnet_exists(netuid), ( + "Subnet wasn't created successfully" + ) # Test add_stake fee - stake_fee_0 = subtensor.get_stake_add_fee( + stake_fee_0 = await async_subtensor.staking.get_stake_add_fee( amount=stake_amount, netuid=netuid, coldkey_ss58=alice_wallet.coldkeypub.ss58_address, @@ -38,7 +177,7 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): ) # Test unstake fee - unstake_fee_root = subtensor.get_unstake_fee( + unstake_fee_root = await async_subtensor.staking.get_unstake_fee( amount=stake_amount, netuid=root_netuid, coldkey_ss58=alice_wallet.coldkeypub.ss58_address, @@ -96,7 +235,7 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): ] for scenario in movement_scenarios: - stake_fee = subtensor.get_stake_movement_fee( + stake_fee = await async_subtensor.staking.get_stake_movement_fee( amount=stake_amount, origin_netuid=scenario["origin_netuid"], origin_hotkey_ss58=scenario["origin_hotkey"], @@ -112,12 +251,14 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): # Test cross-subnet movement netuid2 = 3 - assert subtensor.register_subnet(alice_wallet), ( + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the second subnet" ) - assert subtensor.subnet_exists(netuid2), "Second subnet wasn't created successfully" + assert await async_subtensor.subnets.subnet_exists(netuid2), ( + "Second subnet wasn't created successfully" + ) - stake_fee = subtensor.get_stake_movement_fee( + stake_fee = await async_subtensor.staking.get_stake_movement_fee( amount=stake_amount, origin_netuid=netuid, origin_hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -130,3 +271,4 @@ async def test_stake_fee_api(local_chain, subtensor, alice_wallet, bob_wallet): assert stake_fee >= min_stake_fee, ( "Stake fee should be greater than the minimum stake fee" ) + logging.console.success("✅ Passed [blue]test_stake_fee_api_async[/blue]") From 887bd78d702a03a1224a2e9e7d11d44a3e3b0cd7 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 14:53:01 -0700 Subject: [PATCH 40/86] logging --- tests/e2e_tests/test_commit_weights.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 8266f19d00..c219e63b96 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -465,7 +465,7 @@ def send_commit(salt_, weight_uids_, weight_vals_): assert len(weight_commits.value) == AMOUNT_OF_COMMIT_WEIGHTS, ( "Expected exact list of weight commits" ) - logging.console.success("Passed `test_commit_and_reveal_weights` test.") + logging.console.success("✅ Passed `test_commit_and_reveal_weights` test.") @pytest.mark.asyncio @@ -652,4 +652,4 @@ async def send_commit_(): assert len(weight_commits.value) == AMOUNT_OF_COMMIT_WEIGHTS, ( "Expected exact list of weight commits" ) - logging.console.success("Passed `test_commit_and_reveal_weights_async` test.") + logging.console.success("✅ Passed `test_commit_and_reveal_weights_async` test.") From 9331b9994af842c955cee6526f5c3fb710c4c361 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 16:16:33 -0700 Subject: [PATCH 41/86] improve SubtensorApi --- bittensor/core/subtensor_api/staking.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bittensor/core/subtensor_api/staking.py b/bittensor/core/subtensor_api/staking.py index 40464e7ccd..a85d88250e 100644 --- a/bittensor/core/subtensor_api/staking.py +++ b/bittensor/core/subtensor_api/staking.py @@ -23,6 +23,8 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_stake_weight = subtensor.get_stake_weight self.get_unstake_fee = subtensor.get_unstake_fee self.move_stake = subtensor.move_stake + self.swap_stake = subtensor.swap_stake + self.transfer_stake = subtensor.transfer_stake self.unstake = subtensor.unstake self.unstake_all = subtensor.unstake_all self.unstake_multiple = subtensor.unstake_multiple From 87ea7115bf469948f69228b4fa797a99931918e7 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 16:50:47 -0700 Subject: [PATCH 42/86] async tests for `tests/e2e_tests/test_staking.py` --- tests/e2e_tests/test_staking.py | 1583 ++++++++++++++++++++++++++----- 1 file changed, 1355 insertions(+), 228 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index b0c21b6c22..f9f86adc5b 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -1,14 +1,19 @@ import pytest +import asyncio from bittensor import logging from bittensor.core.chain_data.stake_info import StakeInfo from bittensor.core.errors import ChainError from bittensor.utils.balance import Balance from tests.e2e_tests.utils.chain_interactions import ( + async_sudo_set_admin_utils, get_dynamic_balance, sudo_set_admin_utils, ) -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) from tests.helpers.helpers import CloseInValue @@ -19,26 +24,28 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): - Unstaking using `unstake` - Checks StakeInfo """ - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + logging.console.info("Testing [blue]test_single_operation[/blue]") + + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register root as Alice - the subnet owner and validator - assert subtensor.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.register_subnet(alice_wallet, True, True) # Verify subnet created successfully - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - subtensor.burned_register( + subtensor.subnets.burned_register( wallet=alice_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - subtensor.burned_register( + subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, @@ -46,7 +53,7 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): ) logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") - stake = subtensor.get_stake( + stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -54,7 +61,7 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): assert stake == Balance(0).set_unit(alice_subnet_netuid) - success = subtensor.add_stake( + success = subtensor.staking.add_stake( wallet=alice_wallet, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -66,14 +73,14 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): assert success is True - stake_alice = subtensor.get_stake( + stake_alice = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, ) logging.console.info(f"Alice stake: {stake_alice}") - stake_bob = subtensor.get_stake( + stake_bob = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -82,7 +89,7 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): logging.console.info(f"Bob stake: {stake_bob}") assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) - stakes = subtensor.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) expected_stakes = [ StakeInfo( @@ -112,16 +119,19 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): is_registered=True, ) ] - if subtensor.is_fast_blocks() + if subtensor.chain.is_fast_blocks() else [] ) expected_stakes += fast_blocks_stake assert stakes == expected_stakes - assert subtensor.get_stake_for_coldkey == subtensor.get_stake_info_for_coldkey + assert ( + subtensor.staking.get_stake_for_coldkey + == subtensor.staking.get_stake_info_for_coldkey + ) - stakes = subtensor.get_stake_for_coldkey_and_hotkey( + stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( alice_wallet.coldkey.ss58_address, bob_wallet.hotkey.ss58_address, ) @@ -160,7 +170,7 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): } # unstale all to check in later - success = subtensor.unstake( + success = subtensor.staking.unstake( wallet=alice_wallet, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -171,7 +181,7 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): assert success is True - stake = subtensor.get_stake( + stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -182,6 +192,194 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): logging.console.success("✅ Test [green]test_single_operation[/green] passed") +@pytest.mark.asyncio +async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet): + """ + Async ests: + - Staking using `add_stake` + - Unstaking using `unstake` + - Checks StakeInfo + """ + logging.console.info("Testing [blue]test_single_operation_async[/blue]") + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + # Register root as Alice - the subnet owner and validator + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True) + + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + success = await async_subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1), + wait_for_inclusion=True, + wait_for_finalization=True, + period=16, + ) + + assert success is True + + stake_alice = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake: {stake_alice}") + + stake_bob = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + logging.console.info(f"Bob stake: {stake_bob}") + assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) + + stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + expected_stakes = [ + StakeInfo( + hotkey_ss58=stakes[0].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + + fast_blocks_stake = ( + [ + StakeInfo( + hotkey_ss58=stakes[1].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + stakes[1].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ) + ] + if await async_subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_blocks_stake + + assert stakes == expected_stakes + assert ( + async_subtensor.staking.get_stake_for_coldkey + == async_subtensor.staking.get_stake_info_for_coldkey + ) + + stakes = await async_subtensor.staking.get_stake_for_coldkey_and_hotkey( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + ) + + assert stakes == { + 0: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=0, + stake=Balance(0), + locked=Balance(0), + emission=Balance(0), + drain=0, + is_registered=False, + ), + 1: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=1, + stake=stake.set_unit(1), + locked=Balance.from_tao(0, netuid=1), + emission=Balance.from_tao(0, netuid=1), + drain=0, + is_registered=False, + ), + 2: StakeInfo( + hotkey_ss58=bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[2].stake.rao, alice_subnet_netuid), + locked=Balance.from_tao(0, netuid=alice_subnet_netuid), + emission=get_dynamic_balance(stakes[2].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + } + + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake before unstake: {stake}") + + # unstale all to check in later + success, message = await async_subtensor.staking.unstake_all( + wallet=alice_wallet, + hotkey=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + period=16, + ) + assert success is True, message + + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"Alice stake after unstake: {stake}") + + # all balances have been unstaked + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + logging.console.success("✅ Test [green]test_single_operation_async[/green] passed") + + def test_batch_operations(subtensor, alice_wallet, bob_wallet): """ Tests: @@ -190,6 +388,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): - Checks StakeInfo - Checks Accounts Balance """ + logging.console.info("Testing [blue]test_batch_operations[/blue]") netuids = [ 2, @@ -197,7 +396,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): ] for _ in netuids: - subtensor.register_subnet( + subtensor.subnets.register_subnet( alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, @@ -208,7 +407,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, alice_wallet, netuid) for netuid in netuids: - subtensor.burned_register( + subtensor.subnets.burned_register( bob_wallet, netuid, wait_for_inclusion=True, @@ -216,7 +415,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): ) for netuid in netuids: - stake = subtensor.get_stake( + stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, bob_wallet.hotkey.ss58_address, netuid=netuid, @@ -224,7 +423,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - balances = subtensor.get_balances( + balances = subtensor.wallets.get_balances( alice_wallet.coldkey.ss58_address, bob_wallet.coldkey.ss58_address, ) @@ -242,7 +441,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): alice_balance = balances[alice_wallet.coldkey.ss58_address] - success = subtensor.add_stake_multiple( + success = subtensor.staking.add_stake_multiple( alice_wallet, hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], netuids=netuids, @@ -252,7 +451,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): assert success is True stakes = [ - subtensor.get_stake( + subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, bob_wallet.hotkey.ss58_address, netuid=netuid, @@ -265,7 +464,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): alice_balance -= len(netuids) * Balance.from_tao(10_000) - balances = subtensor.get_balances( + balances = subtensor.wallets.get_balances( alice_wallet.coldkey.ss58_address, bob_wallet.coldkey.ss58_address, ) @@ -296,11 +495,11 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): call, alice_wallet.coldkeypub ) fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) - dynamic_info = subtensor.subnet(netuid) + dynamic_info = subtensor.subnets.subnet(netuid) fee_tao = dynamic_info.alpha_to_tao(fee_alpha) expected_fee_paid += fee_tao - success = subtensor.unstake_multiple( + success = subtensor.staking.unstake_multiple( alice_wallet, hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], netuids=netuids, @@ -310,7 +509,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): assert success is True for netuid, old_stake in zip(netuids, stakes): - stake = subtensor.get_stake( + stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, bob_wallet.hotkey.ss58_address, netuid=netuid, @@ -318,7 +517,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): assert stake < old_stake, f"netuid={netuid} stake={stake}" - balances = subtensor.get_balances( + balances = subtensor.wallets.get_balances( alice_wallet.coldkey.ss58_address, bob_wallet.coldkey.ss58_address, ) @@ -331,82 +530,233 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): logging.console.success("✅ Test [green]test_batch_operations[/green] passed") -def test_safe_staking_scenarios( - local_chain, subtensor, alice_wallet, bob_wallet, eve_wallet -): +@pytest.mark.asyncio +async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): """ - Tests safe staking scenarios with different parameters. - - For both staking and unstaking: - 1. Fails with strict threshold (0.5%) and no partial staking - 2. Succeeds with strict threshold (0.5%) and partial staking allowed - 3. Succeeds with lenient threshold (10% and 30%) and no partial staking + Async tests: + - Staking using `add_stake_multiple` + - Unstaking using `unstake_multiple` + - Checks StakeInfo + - Checks Accounts Balance """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - # Register root as Alice - the subnet owner and validator - assert subtensor.extrinsics.register_subnet(alice_wallet, True, True) + logging.console.info("Testing [blue]test_batch_operations_async[/blue]") - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) + netuids = [ + 2, + 3, + ] - # Change the tempo of the subnet - TEMPO_TO_SET = 100 if subtensor.chain.is_fast_blocks() else 20 - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": alice_subnet_netuid, "tempo": TEMPO_TO_SET}, - )[0] - is True - ) - tempo = subtensor.subnets.get_subnet_hyperparameters( - netuid=alice_subnet_netuid - ).tempo - assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." - logging.console.success(f"SN #{alice_subnet_netuid} tempo set to {TEMPO_TO_SET}") + for _ in netuids: + await async_subtensor.subnets.register_subnet( + wallet=alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + # make sure we passed start_call limit for both subnets + for netuid in netuids: + assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) - subtensor.extrinsics.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + for netuid in netuids: + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) - initial_stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - assert initial_stake == Balance(0).set_unit(alice_subnet_netuid) - logging.console.info(f"[orange]Initial stake: {initial_stake}[orange]") + for netuid in netuids: + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) - # Test Staking Scenarios - stake_amount = Balance.from_tao(100) + assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - # 1. Strict params - should fail - success = subtensor.staking.add_stake( - wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - amount=stake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, - safe_staking=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=False, + balances = await async_subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, ) - assert success is False - current_stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) + expected_balances = { + alice_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[alice_wallet.coldkey.ss58_address].rao + ), + bob_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[bob_wallet.coldkey.ss58_address].rao + ), + } + + assert balances == expected_balances + + alice_balance = balances[alice_wallet.coldkey.ss58_address] + + success = await async_subtensor.staking.add_stake_multiple( + alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(10_000) for _ in netuids], + ) + + assert success is True + + stakes = [ + await async_subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + for netuid in netuids + ] + + for netuid, stake in zip(netuids, stakes): + assert stake > Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + + alice_balance -= len(netuids) * Balance.from_tao(10_000) + + balances = await async_subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + expected_balances = { + alice_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[alice_wallet.coldkey.ss58_address].rao + ), + bob_wallet.coldkey.ss58_address: get_dynamic_balance( + balances[bob_wallet.coldkey.ss58_address].rao + ), + } + + assert balances == expected_balances + + expected_fee_paid = Balance(0) + for netuid in netuids: + call = await async_subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake", + call_params={ + "hotkey": bob_wallet.hotkey.ss58_address, + "amount_unstaked": Balance.from_tao(100).rao, + "netuid": netuid, + }, + ) + payment_info = await async_subtensor.substrate.get_payment_info( + call, alice_wallet.coldkeypub + ) + fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) + dynamic_info = await async_subtensor.subnets.subnet(netuid) + fee_tao = dynamic_info.alpha_to_tao(fee_alpha) + expected_fee_paid += fee_tao + + success = await async_subtensor.staking.unstake_multiple( + alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(100) for _ in netuids], + ) + + assert success is True + + for netuid, old_stake in zip(netuids, stakes): + stake = await async_subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + + assert stake < old_stake, f"netuid={netuid} stake={stake}" + + balances = await async_subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, + ) + + assert CloseInValue( # Make sure we are within 0.0001 TAO due to tx fees + balances[bob_wallet.coldkey.ss58_address], Balance.from_rao(100_000) + ) == Balance.from_tao(999_999.7994) + + assert balances[alice_wallet.coldkey.ss58_address] > alice_balance + logging.console.success("✅ Test [green]test_batch_operations_async[/green] passed") + + +def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet): + """ + Tests safe staking scenarios with different parameters. + + For both staking and unstaking: + 1. Fails with strict threshold (0.5%) and no partial staking + 2. Succeeds with strict threshold (0.5%) and partial staking allowed + 3. Succeeds with lenient threshold (10% and 30%) and no partial staking + """ + logging.console.info("Testing [blue]test_safe_staking_scenarios[/blue]") + + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + # Register root as Alice - the subnet owner and validator + assert subtensor.extrinsics.register_subnet(alice_wallet, True, True) + + # Verify subnet created successfully + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + # Change the tempo of the subnet + TEMPO_TO_SET = 100 if subtensor.chain.is_fast_blocks() else 20 + assert ( + sudo_set_admin_utils( + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": alice_subnet_netuid, "tempo": TEMPO_TO_SET}, + )[0] + is True + ) + tempo = subtensor.subnets.get_subnet_hyperparameters( + netuid=alice_subnet_netuid + ).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + logging.console.success(f"SN #{alice_subnet_netuid} tempo set to {TEMPO_TO_SET}") + + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + + subtensor.extrinsics.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + initial_stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + assert initial_stake == Balance(0).set_unit(alice_subnet_netuid) + logging.console.info(f"[orange]Initial stake: {initial_stake}[orange]") + + # Test Staking Scenarios + stake_amount = Balance.from_tao(100) + + # 1. Strict params - should fail + success = subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=stake_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert success is False + + current_stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) assert current_stake == Balance(0).set_unit(alice_subnet_netuid), ( "Stake should not change after failed attempt" ) @@ -501,7 +851,7 @@ def test_safe_staking_scenarios( ) assert success is True - partial_unstake = subtensor.get_stake( + partial_unstake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -512,7 +862,7 @@ def test_safe_staking_scenarios( ) # 3. Higher threshold - should succeed fully - success = subtensor.unstake( + success = subtensor.staking.unstake( wallet=alice_wallet, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -527,69 +877,75 @@ def test_safe_staking_scenarios( logging.console.success("✅ Test [green]test_safe_staking_scenarios[/green] passed") -def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): +@pytest.mark.asyncio +async def test_safe_staking_scenarios_async( + async_subtensor, alice_wallet, bob_wallet, eve_wallet +): """ - Tests safe swap stake scenarios with different parameters. + Tests safe staking scenarios with different parameters. - Tests: - 1. Fails with strict threshold (0.5%) - 2. Succeeds with lenient threshold (10%) + For both staking and unstaking: + 1. Fails with strict threshold (0.5%) and no partial staking + 2. Succeeds with strict threshold (0.5%) and partial staking allowed + 3. Succeeds with lenient threshold (10% and 30%) and no partial staking """ - # Create new subnet (netuid 2) and register Alice - origin_netuid = 2 - assert subtensor.register_subnet(bob_wallet, True, True) - assert subtensor.subnet_exists(origin_netuid), "Subnet wasn't created successfully" - dest_netuid = 3 - assert subtensor.register_subnet(bob_wallet, True, True) - assert subtensor.subnet_exists(dest_netuid), "Subnet wasn't created successfully" + logging.console.info("Testing [blue]test_safe_staking_scenarios_async[/blue]") - # make sure we passed start_call limit for both subnets - assert wait_to_start_call(subtensor, bob_wallet, origin_netuid) - assert wait_to_start_call(subtensor, bob_wallet, dest_netuid) + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + # Register root as Alice - the subnet owner and validator + assert await async_subtensor.extrinsics.register_subnet(alice_wallet, True, True) - # Register Alice on both subnets - subtensor.burned_register( - alice_wallet, - netuid=origin_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, + # Verify subnet created successfully + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" ) - subtensor.burned_register( - alice_wallet, - netuid=dest_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, + + # Change the tempo of the subnet + TEMPO_TO_SET = 100 if await async_subtensor.chain.is_fast_blocks() else 20 + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": alice_subnet_netuid, "tempo": TEMPO_TO_SET}, + ) + )[0] is True + tempo = ( + await async_subtensor.subnets.get_subnet_hyperparameters( + netuid=alice_subnet_netuid + ) + ).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + logging.console.success(f"SN #{alice_subnet_netuid} tempo set to {TEMPO_TO_SET}") + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid ) - # Add initial stake to swap from - initial_stake_amount = Balance.from_tao(10_000) - success = subtensor.add_stake( - alice_wallet, - alice_wallet.hotkey.ss58_address, - netuid=origin_netuid, - amount=initial_stake_amount, + await async_subtensor.extrinsics.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - assert success is True - origin_stake = subtensor.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=origin_netuid, - ) - assert origin_stake > Balance(0).set_unit(origin_netuid), ( - "Origin stake should be non-zero" + initial_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, ) + assert initial_stake == Balance(0).set_unit(alice_subnet_netuid) + logging.console.info(f"[orange]Initial stake: {initial_stake}[orange]") - stake_swap_amount = Balance.from_tao(10_000) - # 1. Try swap with strict threshold and big amount- should fail - success = subtensor.swap_stake( + # Test Staking Scenarios + stake_amount = Balance.from_tao(100) + + # 1. Strict params - should fail + success = await async_subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, - amount=stake_swap_amount, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=stake_amount, wait_for_inclusion=True, wait_for_finalization=True, safe_staking=True, @@ -598,22 +954,226 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): ) assert success is False - # Verify no stake was moved - dest_stake = subtensor.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, + current_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, ) - assert dest_stake == Balance(0).set_unit(dest_netuid), ( - "Destination stake should remain 0 after failed swap" + assert current_stake == Balance(0).set_unit(alice_subnet_netuid), ( + "Stake should not change after failed attempt" ) + logging.console.info(f"[orange]Current stake: {current_stake}[orange]") - # 2. Try swap with higher threshold and less amount - should succeed - stake_swap_amount = Balance.from_tao(100) - success = subtensor.swap_stake( + # 2. Partial allowed - should succeed partially + success = await async_subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=stake_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=True, + ) + assert success is True + + partial_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + assert partial_stake > Balance(0).set_unit(alice_subnet_netuid), ( + "Partial stake should be added" + ) + assert partial_stake < stake_amount, ( + "Partial stake should be less than requested amount" + ) + + # 3. Higher threshold - should succeed fully + amount = Balance.from_tao(100) + success = await async_subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.22, # 22% + allow_partial_stake=False, + ) + assert success is True + + full_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + # Test Unstaking Scenarios + # 1. Strict params - should fail + success = await async_subtensor.staking.unstake( + wallet=alice_wallet, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=full_stake, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert success is False, "Unstake should fail." + + current_stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + logging.console.info(f"[orange]Current stake: {current_stake}[orange]") + logging.console.info(f"[orange]Full stake: {full_stake}[orange]") + + assert current_stake == full_stake, ( + "Stake should not change after failed unstake attempt" + ) + + # 2. Partial allowed - should succeed partially + success = await async_subtensor.staking.unstake( + wallet=alice_wallet, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=current_stake, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=True, + ) + assert success is True + + partial_unstake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + logging.console.info(f"[orange]Partial unstake: {partial_unstake}[orange]") + assert partial_unstake > Balance(0).set_unit(alice_subnet_netuid), ( + "Some stake should remain" + ) + + # 3. Higher threshold - should succeed fully + success = await async_subtensor.staking.unstake( + wallet=alice_wallet, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=partial_unstake, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.3, # 30% + allow_partial_stake=False, + ) + assert success is True, "Unstake should succeed" + logging.console.success( + "✅ Test [green]test_safe_staking_scenarios_async[/green] passed" + ) + + +def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): + """ + Tests safe swap stake scenarios with different parameters. + + Tests: + 1. Fails with strict threshold (0.5%) + 2. Succeeds with lenient threshold (10%) + """ + logging.console.info("Testing [blue]test_safe_swap_stake_scenarios[/blue]") + + # Create new subnet (netuid 2) and register Alice + origin_netuid = 2 + assert subtensor.subnets.register_subnet(bob_wallet, True, True) + assert subtensor.subnets.subnet_exists(origin_netuid), ( + "Subnet wasn't created successfully" + ) + dest_netuid = 3 + assert subtensor.subnets.register_subnet(bob_wallet, True, True) + assert subtensor.subnets.subnet_exists(dest_netuid), ( + "Subnet wasn't created successfully" + ) + + # make sure we passed start_call limit for both subnets + assert wait_to_start_call(subtensor, bob_wallet, origin_netuid) + assert wait_to_start_call(subtensor, bob_wallet, dest_netuid) + + # Register Alice on both subnets + subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=origin_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dest_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Add initial stake to swap from + initial_stake_amount = Balance.from_tao(10_000) + success = subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=origin_netuid, + amount=initial_stake_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success is True + + origin_stake = subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=origin_netuid, + ) + assert origin_stake > Balance(0).set_unit(origin_netuid), ( + "Origin stake should be non-zero" + ) + + stake_swap_amount = Balance.from_tao(10_000) + # 1. Try swap with strict threshold and big amount- should fail + success = subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=dest_netuid, + amount=stake_swap_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert success is False + + # Verify no stake was moved + dest_stake = subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=dest_netuid, + ) + assert dest_stake == Balance(0).set_unit(dest_netuid), ( + "Destination stake should remain 0 after failed swap" + ) + + # 2. Try swap with higher threshold and less amount - should succeed + stake_swap_amount = Balance.from_tao(100) + success = subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, destination_netuid=dest_netuid, amount=stake_swap_amount, wait_for_inclusion=True, @@ -625,12 +1185,127 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): assert success is True # Verify stake was moved - origin_stake = subtensor.get_stake( # TODO this seems unused + dest_stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, + netuid=dest_netuid, + ) + assert dest_stake > Balance(0).set_unit(dest_netuid), ( + "Destination stake should be non-zero after successful swap" + ) + logging.console.success( + "✅ Test [green]test_safe_swap_stake_scenarios[/green] passed" + ) + + +@pytest.mark.asyncio +async def test_safe_swap_stake_scenarios_async( + async_subtensor, alice_wallet, bob_wallet +): + """ + Tests safe swap stake scenarios with different parameters. + + Tests: + 1. Fails with strict threshold (0.5%) + 2. Succeeds with lenient threshold (10%) + """ + logging.console.info("Testing [blue]test_safe_swap_stake_scenarios_async[/blue]") + + # Create new subnet (netuid 2) and register Alice + origin_netuid = 2 + assert await async_subtensor.subnets.register_subnet(bob_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(origin_netuid), ( + "Subnet wasn't created successfully" + ) + dest_netuid = 3 + assert await async_subtensor.subnets.register_subnet(bob_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(dest_netuid), ( + "Subnet wasn't created successfully" + ) + + # make sure we passed start_call limit for both subnets + assert await async_wait_to_start_call(async_subtensor, bob_wallet, origin_netuid) + assert await async_wait_to_start_call(async_subtensor, bob_wallet, dest_netuid) + + # Register Alice on both subnets + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, netuid=origin_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dest_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Add initial stake to swap from + initial_stake_amount = Balance.from_tao(10_000) + success = await async_subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=origin_netuid, + amount=initial_stake_amount, + wait_for_inclusion=True, + wait_for_finalization=True, ) - dest_stake = subtensor.get_stake( + assert success is True + + origin_stake = await async_subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=origin_netuid, + ) + assert origin_stake > Balance(0).set_unit(origin_netuid), ( + "Origin stake should be non-zero" + ) + + stake_swap_amount = Balance.from_tao(10_000) + # 1. Try swap with strict threshold and big amount- should fail + success = await async_subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=dest_netuid, + amount=stake_swap_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert success is False + + # Verify no stake was moved + dest_stake = await async_subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + alice_wallet.hotkey.ss58_address, + netuid=dest_netuid, + ) + assert dest_stake == Balance(0).set_unit(dest_netuid), ( + "Destination stake should remain 0 after failed swap" + ) + + # 2. Try swap with higher threshold and less amount - should succeed + stake_swap_amount = Balance.from_tao(100) + success = await async_subtensor.staking.swap_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=dest_netuid, + amount=stake_swap_amount, + wait_for_inclusion=True, + wait_for_finalization=True, + safe_staking=True, + rate_tolerance=0.3, # 30% + allow_partial_stake=True, + ) + assert success is True + + # Verify stake was moved + dest_stake = await async_subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, netuid=dest_netuid, @@ -639,27 +1314,195 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): "Destination stake should be non-zero after successful swap" ) logging.console.success( - "✅ Test [green]test_safe_swap_stake_scenarios[/green] passed" + "✅ Test [green]test_safe_swap_stake_scenarios_async[/green] passed" + ) + + +def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): + """ + Tests: + - Adding stake + - Moving stake from one hotkey-subnet pair to another + - Testing `move_stake` method with `move_all_stake=True` flag. + """ + logging.console.info("Testing [blue]test_move_stake[/blue]") + + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1_000), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + + assert stakes == [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + + bob_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 + subtensor.subnets.register_subnet(bob_wallet, True, True) + assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( + "Subnet wasn't created successfully" ) + assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) + + subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert subtensor.staking.move_stake( + wallet=alice_wallet, + origin_hotkey=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_hotkey=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + amount=stakes[0].stake, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + + stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + + expected_stakes = [ + StakeInfo( + hotkey_ss58=stakes[0].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid + if subtensor.chain.is_fast_blocks() + else bob_subnet_netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), + locked=Balance(0).set_unit(bob_subnet_netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, bob_subnet_netuid), + drain=0, + is_registered=True, + ) + ] + + fast_block_stake = ( + [ + StakeInfo( + hotkey_ss58=stakes[1].hotkey_ss58, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=bob_subnet_netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, bob_subnet_netuid), + locked=Balance(0).set_unit(bob_subnet_netuid), + emission=get_dynamic_balance(stakes[1].emission.rao, bob_subnet_netuid), + drain=0, + is_registered=True, + ), + ] + if subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_block_stake + assert stakes == expected_stakes + + # test move_stake with move_all_stake=True + dave_stake = subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") + + assert subtensor.staking.add_stake( + wallet=dave_wallet, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + amount=Balance.from_tao(1000), + wait_for_inclusion=True, + wait_for_finalization=True, + allow_partial_stake=True, + ) + + dave_stake = subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") + + # let chain to process the transaction + subtensor.wait_for_block( + subtensor.block + subtensor.subnets.tempo(netuid=bob_subnet_netuid) + ) + + assert subtensor.staking.move_stake( + wallet=dave_wallet, + origin_hotkey=dave_wallet.hotkey.ss58_address, + origin_netuid=bob_subnet_netuid, + destination_hotkey=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + move_all_stake=True, + ) + + dave_stake = subtensor.staking.get_stake( + coldkey_ss58=dave_wallet.coldkey.ss58_address, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + ) + logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") + + assert dave_stake.rao == CloseInValue(0, 0.00001) + + logging.console.success("✅ Test [green]test_move_stake[/green] passed.") -def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): + +@pytest.mark.asyncio +async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): """ Tests: - Adding stake - Moving stake from one hotkey-subnet pair to another - Testing `move_stake` method with `move_all_stake=True` flag. """ + logging.console.info("Testing [blue]test_move_stake_async[/blue]") - alice_subnet_netuid = subtensor.get_total_subnets() # 2 - assert subtensor.register_subnet(alice_wallet, True, True) - assert subtensor.subnet_exists(alice_subnet_netuid), ( + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) - assert subtensor.add_stake( + assert await async_subtensor.staking.add_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -668,7 +1511,9 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_finalization=True, ) - stakes = subtensor.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) assert stakes == [ StakeInfo( @@ -683,29 +1528,31 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ), ] - bob_subnet_netuid = subtensor.get_total_subnets() # 3 - subtensor.register_subnet(bob_wallet, True, True) - assert subtensor.subnet_exists(bob_subnet_netuid), ( + bob_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 + await async_subtensor.subnets.register_subnet(bob_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( "Subnet wasn't created successfully" ) - assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) + assert await async_wait_to_start_call( + async_subtensor, bob_wallet, bob_subnet_netuid + ) - subtensor.burned_register( + await async_subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - subtensor.burned_register( + await async_subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - assert subtensor.move_stake( + assert await async_subtensor.staking.move_stake( wallet=alice_wallet, origin_hotkey=alice_wallet.hotkey.ss58_address, origin_netuid=alice_subnet_netuid, @@ -716,14 +1563,16 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_inclusion=True, ) - stakes = subtensor.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) expected_stakes = [ StakeInfo( hotkey_ss58=stakes[0].hotkey_ss58, coldkey_ss58=alice_wallet.coldkey.ss58_address, netuid=alice_subnet_netuid - if subtensor.is_fast_blocks() + if await async_subtensor.chain.is_fast_blocks() else bob_subnet_netuid, stake=get_dynamic_balance(stakes[0].stake.rao, bob_subnet_netuid), locked=Balance(0).set_unit(bob_subnet_netuid), @@ -746,7 +1595,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): is_registered=True, ), ] - if subtensor.is_fast_blocks() + if await async_subtensor.chain.is_fast_blocks() else [] ) @@ -754,14 +1603,14 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert stakes == expected_stakes # test move_stake with move_all_stake=True - dave_stake = subtensor.staking.get_stake( + dave_stake = await async_subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=bob_subnet_netuid, ) logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") - assert subtensor.staking.add_stake( + assert await async_subtensor.staking.add_stake( wallet=dave_wallet, hotkey_ss58=dave_wallet.hotkey.ss58_address, netuid=bob_subnet_netuid, @@ -771,19 +1620,20 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): allow_partial_stake=True, ) - dave_stake = subtensor.staking.get_stake( + dave_stake = await async_subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=dave_wallet.hotkey.ss58_address, netuid=bob_subnet_netuid, ) logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") - # let chain to process the transaction - subtensor.wait_for_block( - subtensor.block + subtensor.subnets.tempo(netuid=bob_subnet_netuid) + block_, tampo_ = await asyncio.gather( + async_subtensor.block, async_subtensor.subnets.tempo(netuid=bob_subnet_netuid) ) + # let chain to process the transaction + await async_subtensor.wait_for_block(block_ + tampo_) - assert subtensor.staking.move_stake( + assert await async_subtensor.staking.move_stake( wallet=dave_wallet, origin_hotkey=dave_wallet.hotkey.ss58_address, origin_netuid=bob_subnet_netuid, @@ -794,7 +1644,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): move_all_stake=True, ) - dave_stake = subtensor.staking.get_stake( + dave_stake = await async_subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=dave_wallet.hotkey.ss58_address, netuid=bob_subnet_netuid, @@ -803,7 +1653,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert dave_stake.rao == CloseInValue(0, 0.00001) - logging.console.success("✅ Test [green]test_move_stake[/green] passed.") + logging.console.success("✅ Test [green]test_move_stake_async[/green] passed.") def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -812,23 +1662,25 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - Adding stake - Transferring stake from one coldkey-subnet pair to another """ - alice_subnet_netuid = subtensor.get_total_subnets() # 2 + logging.console.info("Testing [blue]test_transfer_stake[/blue]") + + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.register_subnet(alice_wallet, True, True) - assert subtensor.subnet_exists(alice_subnet_netuid), ( + assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - subtensor.burned_register( + subtensor.subnets.burned_register( alice_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - assert subtensor.add_stake( + assert subtensor.staking.add_stake( alice_wallet, alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, @@ -837,7 +1689,9 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_finalization=True, ) - alice_stakes = subtensor.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + alice_stakes = subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) assert alice_stakes == [ StakeInfo( @@ -854,23 +1708,25 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ), ] - bob_stakes = subtensor.get_stake_for_coldkey(bob_wallet.coldkey.ss58_address) + bob_stakes = subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) assert bob_stakes == [] - dave_subnet_netuid = subtensor.get_total_subnets() # 3 - subtensor.register_subnet(dave_wallet, True, True) + dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 + subtensor.subnets.register_subnet(dave_wallet, True, True) assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) - subtensor.burned_register( + subtensor.subnets.burned_register( bob_wallet, netuid=dave_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, ) - assert subtensor.transfer_stake( + assert subtensor.staking.transfer_stake( alice_wallet, destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, hotkey_ss58=alice_wallet.hotkey.ss58_address, @@ -881,7 +1737,9 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_finalization=True, ) - alice_stakes = subtensor.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + alice_stakes = subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) expected_alice_stake = ( [ @@ -900,13 +1758,15 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): is_registered=True, ), ] - if subtensor.is_fast_blocks() + if subtensor.chain.is_fast_blocks() else [] ) assert alice_stakes == expected_alice_stake - bob_stakes = subtensor.get_stake_for_coldkey(bob_wallet.coldkey.ss58_address) + bob_stakes = subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) expected_bob_stake = [ StakeInfo( @@ -926,6 +1786,143 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): logging.console.success("✅ Test [green]test_transfer_stake[/green] passed") +@pytest.mark.asyncio +async def test_transfer_stake_async( + async_subtensor, alice_wallet, bob_wallet, dave_wallet +): + """ + Tests: + - Adding stake + - Transferring stake from one coldkey-subnet pair to another + """ + logging.console.info("Testing [blue]test_transfer_stake_async[/blue]") + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid + ) + + await async_subtensor.subnets.burned_register( + alice_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert await async_subtensor.staking.add_stake( + alice_wallet, + alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1_000), + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + assert alice_stakes == [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_subnet_netuid), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + alice_stakes[0].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ), + ] + + bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + + assert bob_stakes == [] + + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 + await async_subtensor.subnets.register_subnet(dave_wallet, True, True) + + assert await async_wait_to_start_call( + async_subtensor, dave_wallet, dave_subnet_netuid + ) + + await async_subtensor.subnets.burned_register( + bob_wallet, + netuid=dave_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert await async_subtensor.staking.transfer_stake( + alice_wallet, + destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_netuid=dave_subnet_netuid, + amount=alice_stakes[0].stake, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_wallet.coldkey.ss58_address + ) + + expected_alice_stake = ( + [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + netuid=alice_subnet_netuid, + stake=get_dynamic_balance( + alice_stakes[0].stake.rao, alice_subnet_netuid + ), + locked=Balance(0).set_unit(alice_subnet_netuid), + emission=get_dynamic_balance( + alice_stakes[0].emission.rao, alice_subnet_netuid + ), + drain=0, + is_registered=True, + ), + ] + if await async_subtensor.chain.is_fast_blocks() + else [] + ) + + assert alice_stakes == expected_alice_stake + + bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + + expected_bob_stake = [ + StakeInfo( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + coldkey_ss58=bob_wallet.coldkey.ss58_address, + netuid=dave_subnet_netuid, + stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_subnet_netuid), + locked=Balance(0).set_unit(dave_subnet_netuid), + emission=get_dynamic_balance( + bob_stakes[0].emission.rao, dave_subnet_netuid + ), + drain=0, + is_registered=False, + ), + ] + assert bob_stakes == expected_bob_stake + logging.console.success("✅ Test [green]test_transfer_stake_async[/green] passed") + + # For test we set rate_tolerance=0.7 (70%) because of price is highly dynamic for fast-blocks and 2 SN to avoid ` # Slippage is too high for the transaction`. This logic controls by the chain. # Also this test implementation works with non-fast-blocks run. @@ -941,32 +1938,26 @@ def test_unstaking_with_limit( subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance ): """Test unstaking with limits goes well for all subnets with and without price limit.""" + logging.console.info("Testing [blue]test_unstaking_with_limit[/blue]") # Register first SN - alice_subnet_netuid_2 = subtensor.get_total_subnets() # 2 - assert subtensor.register_subnet(alice_wallet, True, True) - assert subtensor.subnet_exists(alice_subnet_netuid_2), ( + alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( "Subnet wasn't created successfully" ) wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_2) - assert subtensor.start_call( - alice_wallet, - netuid=alice_subnet_netuid_2, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - # Register Bob and Dave in SN2 - assert subtensor.burned_register( + assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid_2, wait_for_inclusion=True, wait_for_finalization=True, ) - assert subtensor.burned_register( + assert subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid_2, wait_for_inclusion=True, @@ -974,30 +1965,23 @@ def test_unstaking_with_limit( ) # Register second SN - alice_subnet_netuid_3 = subtensor.get_total_subnets() # 3 - assert subtensor.register_subnet(alice_wallet, True, True) - assert subtensor.subnet_exists(alice_subnet_netuid_3), ( + alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 + assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( "Subnet wasn't created successfully" ) wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid_3) - assert subtensor.start_call( - alice_wallet, - netuid=alice_subnet_netuid_3, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - # Register Bob and Dave in SN3 - assert subtensor.burned_register( + assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid_3, wait_for_inclusion=True, wait_for_finalization=True, ) - assert subtensor.burned_register( + assert subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid_3, wait_for_inclusion=True, @@ -1005,11 +1989,14 @@ def test_unstaking_with_limit( ) # Check Bob's stakes are empty. - assert subtensor.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) == [] + assert ( + subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) + == [] + ) # Bob stakes to Dave in both SNs - assert subtensor.add_stake( + assert subtensor.staking.add_stake( wallet=bob_wallet, hotkey_ss58=dave_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid_2, @@ -1018,7 +2005,7 @@ def test_unstaking_with_limit( wait_for_finalization=True, period=16, ), f"Cant add stake to dave in SN {alice_subnet_netuid_2}" - assert subtensor.add_stake( + assert subtensor.staking.add_stake( wallet=bob_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid_3, @@ -1029,7 +2016,9 @@ def test_unstaking_with_limit( ), f"Cant add stake to dave in SN {alice_subnet_netuid_3}" # Check that both stakes are presented in result - bob_stakes = subtensor.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) + bob_stakes = subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) assert len(bob_stakes) == 2 if rate_tolerance == 0.0001: @@ -1037,7 +2026,7 @@ def test_unstaking_with_limit( with pytest.raises( ChainError, match="Slippage is too high for the transaction" ): - subtensor.unstake_all( + subtensor.staking.unstake_all( wallet=bob_wallet, hotkey=bob_stakes[0].hotkey_ss58, netuid=bob_stakes[0].netuid, @@ -1048,7 +2037,7 @@ def test_unstaking_with_limit( else: # Successful cases for si in bob_stakes: - assert subtensor.unstake_all( + assert subtensor.staking.unstake_all( wallet=bob_wallet, hotkey=si.hotkey_ss58, netuid=si.netuid, @@ -1058,7 +2047,145 @@ def test_unstaking_with_limit( )[0] # Make sure both unstake were successful. - bob_stakes = subtensor.get_stake_info_for_coldkey( + bob_stakes = subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + assert len(bob_stakes) == 0 + + +@pytest.mark.parametrize( + "rate_tolerance", + [None, 1.0], + ids=[ + "Without price limit", + "With price limit", + ], +) +@pytest.mark.asyncio +async def test_unstaking_with_limit_async( + async_subtensor, alice_wallet, bob_wallet, dave_wallet, rate_tolerance +): + """Test unstaking with limits goes well for all subnets with and without price limit.""" + logging.console.info("Testing [blue]test_unstaking_with_limit_async[/blue]") + + # Register first SN + alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, alice_wallet, alice_subnet_netuid_2 + ) + + # Register Bob and Dave in SN2 + assert await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_2, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_2, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Register second SN + alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True) + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( + "Subnet wasn't created successfully" + ) + + await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid_3) + + # Register Bob and Dave in SN3 + assert await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + assert await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_3, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + # Check Bob's stakes are empty. + assert ( + await async_subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + == [] + ) + + # Bob stakes to Dave in both SNs + + assert await async_subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid_2, + amount=Balance.from_tao(10000), + wait_for_inclusion=True, + wait_for_finalization=True, + period=16, + ), f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + assert await async_subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid_3, + amount=Balance.from_tao(15000), + wait_for_inclusion=True, + wait_for_finalization=True, + period=16, + ), f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + + # Check that both stakes are presented in result + bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + assert len(bob_stakes) == 2 + + if rate_tolerance == 0.0001: + # Raise the error + with pytest.raises( + ChainError, match="Slippage is too high for the transaction" + ): + await async_subtensor.staking.unstake_all( + wallet=bob_wallet, + hotkey=bob_stakes[0].hotkey_ss58, + netuid=bob_stakes[0].netuid, + rate_tolerance=rate_tolerance, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + else: + # Successful cases + for si in bob_stakes: + assert ( + await async_subtensor.staking.unstake_all( + wallet=bob_wallet, + hotkey=si.hotkey_ss58, + netuid=si.netuid, + rate_tolerance=rate_tolerance, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + )[0] + + # Make sure both unstake were successful. + bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( bob_wallet.coldkey.ss58_address ) assert len(bob_stakes) == 0 + + logging.console.success( + "✅ Test [green]test_unstaking_with_limit_async[/green] passed" + ) From dba02e449d40f8d1d60ca31ce9cd3c57673dcaf7 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 16:53:01 -0700 Subject: [PATCH 43/86] async tests for `tests/e2e_tests/test_subnets.py` --- tests/e2e_tests/test_subnets.py | 55 ++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/tests/e2e_tests/test_subnets.py b/tests/e2e_tests/test_subnets.py index e9031d56c5..8b28206fdc 100644 --- a/tests/e2e_tests/test_subnets.py +++ b/tests/e2e_tests/test_subnets.py @@ -1,3 +1,7 @@ +import pytest +from bittensor.utils.btlogging import logging + + def test_subnets(subtensor, alice_wallet): """ Tests: @@ -5,30 +9,65 @@ def test_subnets(subtensor, alice_wallet): - Filtering subnets - Checks default TxRateLimit """ + logging.console.info("Testing [blue]test_subnets[/blue]") - subnets = subtensor.all_subnets() - + subnets = subtensor.subnets.all_subnets() assert len(subnets) == 2 - subtensor.register_subnet( + subtensor.subnets.register_subnet( alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, ) - subnets = subtensor.all_subnets() - + subnets = subtensor.subnets.all_subnets() assert len(subnets) == 3 - netuids = subtensor.filter_netuids_by_registered_hotkeys( + netuids = subtensor.wallets.filter_netuids_by_registered_hotkeys( all_netuids=[0, 1, 2], filter_for_netuids=[2], all_hotkeys=[alice_wallet], block=subtensor.block, ) - assert netuids == [2] - tx_rate_limit = subtensor.tx_rate_limit() + tx_rate_limit = subtensor.chain.tx_rate_limit() + assert tx_rate_limit == 1000 + + logging.console.success("✅ Test [green]test_subnets[/green] passed") + + +@pytest.mark.asyncio +async def test_subnets_async(async_subtensor, alice_wallet): + """ + Async tests: + - Querying subnets + - Filtering subnets + - Checks default TxRateLimit + """ + logging.console.info("Testing [blue]test_subnets_async[/blue]") + + subnets = await async_subtensor.subnets.all_subnets() + assert len(subnets) == 2 + + assert await async_subtensor.subnets.register_subnet( + alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + subnets = await async_subtensor.subnets.all_subnets() + assert len(subnets) == 3 + netuids = await async_subtensor.wallets.filter_netuids_by_registered_hotkeys( + all_netuids=[0, 1, 2], + filter_for_netuids=[2], + all_hotkeys=[alice_wallet], + block=await async_subtensor.block, + ) + assert netuids == [2] + + tx_rate_limit = await async_subtensor.chain.tx_rate_limit() assert tx_rate_limit == 1000 + + logging.console.success("✅ Test [green]test_subnets_async[/green] passed") From b58c58b14ecd98d45dd6b0886d17762ce05c0a9f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:04:13 -0700 Subject: [PATCH 44/86] async tests for `tests/e2e_tests/test_subtensor_functions.py` --- tests/e2e_tests/test_subtensor_functions.py | 266 +++++++++++++++++--- 1 file changed, 236 insertions(+), 30 deletions(-) diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 6dbd9805ef..11342caec5 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -1,12 +1,16 @@ import asyncio import pytest - +from bittensor.utils.btlogging import logging from bittensor.utils.balance import Balance from tests.e2e_tests.utils.chain_interactions import ( + async_wait_epoch, wait_epoch, ) -from tests.e2e_tests.utils.e2e_test_utils import wait_to_start_call +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) """ Verifies: @@ -39,36 +43,37 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall 2. Register Alice's neuron 3. Verify Alice and Bob's participation in subnets (individually and global) 4. Verify uids of Alice and Bob gets populated correctly - 5. Start Alice as a validator and verify neuroninfo before/after is different + 5. Start Alice as a validator and verify NeuronInfo before/after is different Raises: AssertionError: If any of the checks or verifications fail """ - netuid = subtensor.get_total_subnets() # 22 + logging.console.info("Testing [blue]test_subtensor_extrinsics[/blue]") + netuid = subtensor.subnets.get_total_subnets() # 22 # Initial balance for Alice, defined in the genesis file of localnet initial_alice_balance = Balance.from_tao(1_000_000) # Current Existential deposit for all accounts in bittensor existential_deposit = Balance.from_tao(0.000_000_500) # Subnets 0 and 1 are bootstrapped from the start - assert subtensor.get_subnets() == [0, 1] - assert subtensor.get_total_subnets() == 2 + assert subtensor.subnets.get_subnets() == [0, 1] + assert subtensor.subnets.get_total_subnets() == 2 # Assert correct balance is fetched for Alice - alice_balance = subtensor.get_balance(alice_wallet.coldkeypub.ss58_address) + alice_balance = subtensor.wallets.get_balance(alice_wallet.coldkeypub.ss58_address) assert alice_balance == initial_alice_balance, ( "Balance for Alice wallet doesn't match with pre-def value" ) # Subnet burn cost is initially lower before we register a subnet - pre_subnet_creation_cost = subtensor.get_subnet_burn_cost() + pre_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() # Register subnet - assert subtensor.register_subnet(alice_wallet, True, True), ( + assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( "Unable to register the subnet" ) # Subnet burn cost is increased immediately after a subnet is registered - post_subnet_creation_cost = subtensor.get_subnet_burn_cost() + post_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() # Assert that the burn cost changed after registering a subnet assert Balance.from_tao(pre_subnet_creation_cost) < Balance.from_tao( @@ -76,77 +81,81 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall ), "Burn cost did not change after subnet creation" # Assert amount is deducted once a subnetwork is registered by Alice - alice_balance_post_sn = subtensor.get_balance(alice_wallet.coldkeypub.ss58_address) + alice_balance_post_sn = subtensor.wallets.get_balance( + alice_wallet.coldkeypub.ss58_address + ) assert alice_balance_post_sn + pre_subnet_creation_cost == initial_alice_balance, ( "Balance is the same even after registering a subnet" ) # Subnet 2 is added after registration - assert subtensor.get_subnets() == [0, 1, 2] - assert subtensor.get_total_subnets() == 3 + assert subtensor.subnets.get_subnets() == [0, 1, 2] + assert subtensor.subnets.get_total_subnets() == 3 # Verify subnet 2 created successfully - assert subtensor.subnet_exists(netuid) + assert subtensor.subnets.subnet_exists(netuid) # Default subnetwork difficulty - assert subtensor.difficulty(netuid) == 10_000_000, ( + assert subtensor.subnets.difficulty(netuid) == 10_000_000, ( "Couldn't fetch correct subnet difficulty" ) # Verify Alice is registered to netuid 2 and Bob isn't registered to any - assert subtensor.get_netuids_for_hotkey( + assert subtensor.wallets.get_netuids_for_hotkey( hotkey_ss58=alice_wallet.hotkey.ss58_address ) == [ netuid, ], "Alice is not registered to netuid 2 as expected" assert ( - subtensor.get_netuids_for_hotkey(hotkey_ss58=bob_wallet.hotkey.ss58_address) + subtensor.wallets.get_netuids_for_hotkey( + hotkey_ss58=bob_wallet.hotkey.ss58_address + ) == [] ), "Bob is unexpectedly registered to some netuid" # Verify Alice's hotkey is registered to any subnet (currently netuid = 2) - assert subtensor.is_hotkey_registered_any( + assert subtensor.wallets.is_hotkey_registered_any( hotkey_ss58=alice_wallet.hotkey.ss58_address ), "Alice's hotkey is not registered to any subnet" - assert not subtensor.is_hotkey_registered_any( + assert not subtensor.wallets.is_hotkey_registered_any( hotkey_ss58=bob_wallet.hotkey.ss58_address ), "Bob's hotkey is unexpectedly registered to a subnet" # Verify netuid = 2 only has Alice registered and not Bob - assert subtensor.is_hotkey_registered_on_subnet( + assert subtensor.wallets.is_hotkey_registered_on_subnet( netuid=netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address ), "Alice's hotkey is not registered on netuid 1" - assert not subtensor.is_hotkey_registered_on_subnet( + assert not subtensor.wallets.is_hotkey_registered_on_subnet( netuid=netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address ), "Bob's hotkey is unexpectedly registered on netuid 1" # Verify Alice's UID on netuid 2 is 0 assert ( - subtensor.get_uid_for_hotkey_on_subnet( + subtensor.subnets.get_uid_for_hotkey_on_subnet( hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid ) == 0 ), "UID for Alice's hotkey on netuid 2 is not 0 as expected" - bob_balance = subtensor.get_balance(bob_wallet.coldkeypub.ss58_address) + bob_balance = subtensor.wallets.get_balance(bob_wallet.coldkeypub.ss58_address) assert wait_to_start_call(subtensor, alice_wallet, netuid) # Register Bob to the subnet - assert subtensor.burned_register(bob_wallet, netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, netuid), ( "Unable to register Bob as a neuron" ) # Verify Bob's UID on netuid 2 is 1 assert ( - subtensor.get_uid_for_hotkey_on_subnet( + subtensor.subnets.get_uid_for_hotkey_on_subnet( hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=netuid ) == 1 ), "UID for Bob's hotkey on netuid 2 is not 1 as expected" # Fetch recycle_amount to register to the subnet - recycle_amount = subtensor.recycle(netuid) + recycle_amount = subtensor.subnets.recycle(netuid) call = subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="burned_register", @@ -157,7 +166,9 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall ) payment_info = subtensor.substrate.get_payment_info(call, bob_wallet.coldkeypub) fee = Balance.from_rao(payment_info["partial_fee"]) - bob_balance_post_reg = subtensor.get_balance(bob_wallet.coldkeypub.ss58_address) + bob_balance_post_reg = subtensor.wallets.get_balance( + bob_wallet.coldkeypub.ss58_address + ) # Ensure recycled amount is only deducted from the balance after registration assert bob_balance - recycle_amount - fee == bob_balance_post_reg, ( @@ -184,12 +195,207 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall # ), "Neuron info not updated after running validator" # Fetch and assert existential deposit for an account in the network - assert subtensor.get_existential_deposit() == existential_deposit, ( + assert subtensor.chain.get_existential_deposit() == existential_deposit, ( "Existential deposit value doesn't match with pre-defined value" ) # Fetching all subnets in the network - all_subnets = subtensor.get_all_subnets_info() + all_subnets = subtensor.subnets.get_all_subnets_info() + + # Assert all netuids are present in all_subnets + expected_netuids = [0, 1, 2] + actual_netuids = [subnet.netuid for subnet in all_subnets] + assert actual_netuids == expected_netuids, ( + f"Expected netuids {expected_netuids}, but found {actual_netuids}" + ) + + # Assert that the owner_ss58 of subnet 2 matches Alice's coldkey address + expected_owner = alice_wallet.coldkeypub.ss58_address + subnet_2 = next((subnet for subnet in all_subnets if subnet.netuid == netuid), None) + actual_owner = subnet_2.owner_ss58 + assert actual_owner == expected_owner, ( + f"Expected owner {expected_owner}, but found {actual_owner}" + ) + + logging.console.success("✅ Passed [blue]test_subtensor_extrinsics[/blue]") + + +@pytest.mark.asyncio +async def test_subtensor_extrinsics_async( + async_subtensor, templates, alice_wallet, bob_wallet +): + """ + Tests subtensor extrinsics + + Steps: + 1. Validate subnets in the chain before/after registering netuid = 1 + 2. Register Alice's neuron + 3. Verify Alice and Bob's participation in subnets (individually and global) + 4. Verify uids of Alice and Bob gets populated correctly + 5. Start Alice as a validator and verify NeuronInfo before/after is different + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing [blue]test_subtensor_extrinsics[/blue]") + netuid = await async_subtensor.subnets.get_total_subnets() # 22 + # Initial balance for Alice, defined in the genesis file of localnet + initial_alice_balance = Balance.from_tao(1_000_000) + # Current Existential deposit for all accounts in bittensor + existential_deposit = Balance.from_tao(0.000_000_500) + + # Subnets 0 and 1 are bootstrapped from the start + assert await async_subtensor.subnets.get_subnets() == [0, 1] + assert await async_subtensor.subnets.get_total_subnets() == 2 + + # Assert correct balance is fetched for Alice + alice_balance = await async_subtensor.wallets.get_balance( + alice_wallet.coldkeypub.ss58_address + ) + assert alice_balance == initial_alice_balance, ( + "Balance for Alice wallet doesn't match with pre-def value" + ) + + # Subnet burn cost is initially lower before we register a subnet + pre_subnet_creation_cost = await async_subtensor.subnets.get_subnet_burn_cost() + + # Register subnet + assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True), ( + "Unable to register the subnet" + ) + + # Subnet burn cost is increased immediately after a subnet is registered + post_subnet_creation_cost = await async_subtensor.subnets.get_subnet_burn_cost() + + # Assert that the burn cost changed after registering a subnet + assert Balance.from_tao(pre_subnet_creation_cost) < Balance.from_tao( + post_subnet_creation_cost + ), "Burn cost did not change after subnet creation" + + # Assert amount is deducted once a subnetwork is registered by Alice + alice_balance_post_sn = await async_subtensor.wallets.get_balance( + alice_wallet.coldkeypub.ss58_address + ) + assert alice_balance_post_sn + pre_subnet_creation_cost == initial_alice_balance, ( + "Balance is the same even after registering a subnet" + ) + + # Subnet 2 is added after registration + assert await async_subtensor.subnets.get_subnets() == [0, 1, 2] + assert await async_subtensor.subnets.get_total_subnets() == 3 + + # Verify subnet 2 created successfully + assert await async_subtensor.subnets.subnet_exists(netuid) + + # Default subnetwork difficulty + assert await async_subtensor.subnets.difficulty(netuid) == 10_000_000, ( + "Couldn't fetch correct subnet difficulty" + ) + + # Verify Alice is registered to netuid 2 and Bob isn't registered to any + assert await async_subtensor.wallets.get_netuids_for_hotkey( + hotkey_ss58=alice_wallet.hotkey.ss58_address + ) == [ + netuid, + ], "Alice is not registered to netuid 2 as expected" + assert ( + await async_subtensor.wallets.get_netuids_for_hotkey( + hotkey_ss58=bob_wallet.hotkey.ss58_address + ) + == [] + ), "Bob is unexpectedly registered to some netuid" + + # Verify Alice's hotkey is registered to any subnet (currently netuid = 2) + assert await async_subtensor.wallets.is_hotkey_registered_any( + hotkey_ss58=alice_wallet.hotkey.ss58_address + ), "Alice's hotkey is not registered to any subnet" + assert not await async_subtensor.wallets.is_hotkey_registered_any( + hotkey_ss58=bob_wallet.hotkey.ss58_address + ), "Bob's hotkey is unexpectedly registered to a subnet" + + # Verify netuid = 2 only has Alice registered and not Bob + assert await async_subtensor.wallets.is_hotkey_registered_on_subnet( + netuid=netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address + ), "Alice's hotkey is not registered on netuid 1" + assert not await async_subtensor.wallets.is_hotkey_registered_on_subnet( + netuid=netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address + ), "Bob's hotkey is unexpectedly registered on netuid 1" + + # Verify Alice's UID on netuid 2 is 0 + assert ( + await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid + ) + == 0 + ), "UID for Alice's hotkey on netuid 2 is not 0 as expected" + + bob_balance = await async_subtensor.wallets.get_balance( + bob_wallet.coldkeypub.ss58_address + ) + + assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) + + # Register Bob to the subnet + assert await async_subtensor.subnets.burned_register(bob_wallet, netuid), ( + "Unable to register Bob as a neuron" + ) + + # Verify Bob's UID on netuid 2 is 1 + assert ( + await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=netuid + ) + == 1 + ), "UID for Bob's hotkey on netuid 2 is not 1 as expected" + + # Fetch recycle_amount to register to the subnet + recycle_amount = await async_subtensor.subnets.recycle(netuid) + call = await async_subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": bob_wallet.hotkey.ss58_address, + }, + ) + payment_info = await async_subtensor.substrate.get_payment_info( + call, bob_wallet.coldkeypub + ) + fee = Balance.from_rao(payment_info["partial_fee"]) + bob_balance_post_reg = await async_subtensor.wallets.get_balance( + bob_wallet.coldkeypub.ss58_address + ) + + # Ensure recycled amount is only deducted from the balance after registration + assert bob_balance - recycle_amount - fee == bob_balance_post_reg, ( + "Balance for Bob is not correct after burned register" + ) + + # neuron_info_old = subtensor.get_neuron_for_pubkey_and_subnet( + # alice_wallet.hotkey.ss58_address, netuid=netuid + # ) + + async with templates.validator(alice_wallet, netuid): + await asyncio.sleep( + 5 + ) # wait for 5 seconds for the metagraph and subtensor to refresh with latest data + + await async_wait_epoch(async_subtensor, netuid) + + # Verify neuron info is updated after running as a validator + # neuron_info = subtensor.get_neuron_for_pubkey_and_subnet( + # alice_wallet.hotkey.ss58_address, netuid=netuid + # ) + # assert ( + # neuron_info_old.dividends != neuron_info.dividends + # ), "Neuron info not updated after running validator" + + # Fetch and assert existential deposit for an account in the network + assert ( + await async_subtensor.chain.get_existential_deposit() == existential_deposit + ), "Existential deposit value doesn't match with pre-defined value" + + # Fetching all subnets in the network + all_subnets = await async_subtensor.subnets.get_all_subnets_info() # Assert all netuids are present in all_subnets expected_netuids = [0, 1, 2] @@ -206,4 +412,4 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall f"Expected owner {expected_owner}, but found {actual_owner}" ) - print("✅ Passed test_subtensor_extrinsics") + logging.console.success("✅ Passed [blue]test_subtensor_extrinsics[/blue]") From 1c5988b324a6b91b4d825d56de3f0d935a65a62a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:32:46 -0700 Subject: [PATCH 45/86] async tests for `tests/e2e_tests/test_transfer.py` --- tests/e2e_tests/test_transfer.py | 102 ++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 21 deletions(-) diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index 0663b540b2..e195e79285 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -12,7 +12,7 @@ logging.set_trace() -def test_transfer(subtensor: "SubtensorApi", alice_wallet): +def test_transfer(subtensor, alice_wallet): """ Test the transfer mechanism on the chain @@ -22,24 +22,70 @@ def test_transfer(subtensor: "SubtensorApi", alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing [blue]test_transfer[/blue]") - print("Testing test_transfer") + transfer_value = Balance.from_tao(2) + dest_coldkey = "5GpzQgpiAKHMWNSH3RN4GLf96GVTDct9QxYEFAY7LWcVzTbx" + + # Fetch transfer fee + transfer_fee = subtensor.wallets.get_transfer_fee( + wallet=alice_wallet, + dest=dest_coldkey, + value=transfer_value, + ) + + # Account details before transfer + balance_before = subtensor.wallets.get_balance(alice_wallet.coldkeypub.ss58_address) + + # Transfer Tao + assert subtensor.extrinsics.transfer( + wallet=alice_wallet, + dest=dest_coldkey, + amount=transfer_value, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + # Account details after transfer + balance_after = subtensor.wallets.get_balance(alice_wallet.coldkeypub.ss58_address) + + # Assert correct transfer calculations + assert balance_before - transfer_fee - transfer_value == balance_after, ( + f"Expected {balance_before - transfer_value - transfer_fee}, got {balance_after}" + ) + + logging.console.success("✅ Passed [blue]test_transfer[/blue]") + + +@pytest.mark.asyncio +async def test_transfer_async(async_subtensor, alice_wallet): + """ + Test the transfer mechanism on the chain + + Steps: + 1. Calculate existing balance and transfer 2 Tao + 2. Calculate balance after transfer call and verify calculations + Raises: + AssertionError: If any of the checks or verifications fail + """ + logging.console.info("Testing [blue]test_transfer[/blue]") transfer_value = Balance.from_tao(2) dest_coldkey = "5GpzQgpiAKHMWNSH3RN4GLf96GVTDct9QxYEFAY7LWcVzTbx" # Fetch transfer fee - transfer_fee = subtensor.get_transfer_fee( + transfer_fee = await async_subtensor.wallets.get_transfer_fee( wallet=alice_wallet, dest=dest_coldkey, value=transfer_value, ) # Account details before transfer - balance_before = subtensor.get_balance(alice_wallet.coldkeypub.ss58_address) + balance_before = await async_subtensor.wallets.get_balance( + alice_wallet.coldkeypub.ss58_address + ) # Transfer Tao - assert subtensor.transfer( + assert await async_subtensor.extrinsics.transfer( wallet=alice_wallet, dest=dest_coldkey, amount=transfer_value, @@ -47,17 +93,21 @@ def test_transfer(subtensor: "SubtensorApi", alice_wallet): wait_for_inclusion=True, ) # Account details after transfer - balance_after = subtensor.get_balance(alice_wallet.coldkeypub.ss58_address) + balance_after = await async_subtensor.wallets.get_balance( + alice_wallet.coldkeypub.ss58_address + ) # Assert correct transfer calculations assert balance_before - transfer_fee - transfer_value == balance_after, ( f"Expected {balance_before - transfer_value - transfer_fee}, got {balance_after}" ) - print("✅ Passed test_transfer") + logging.console.success("✅ Passed [blue]test_transfer[/blue]") -def test_transfer_all(subtensor: "Subtensor", alice_wallet): +def test_transfer_all(subtensor, alice_wallet): + logging.console.info("Testing [blue]test_transfer_all[/blue]") + # create two dummy accounts we can drain dummy_account_1 = Wallet(path="/tmp/bittensor-dummy-account-1") dummy_account_2 = Wallet(path="/tmp/bittensor-dummy-account-2") @@ -65,7 +115,7 @@ def test_transfer_all(subtensor: "Subtensor", alice_wallet): dummy_account_2.create_new_coldkey(use_password=False, overwrite=True) # fund the first dummy account - assert subtensor.transfer( + assert subtensor.extrinsics.transfer( alice_wallet, dest=dummy_account_1.coldkeypub.ss58_address, amount=Balance.from_tao(2.0), @@ -73,8 +123,8 @@ def test_transfer_all(subtensor: "Subtensor", alice_wallet): wait_for_inclusion=True, ) # Account details before transfer - existential_deposit = subtensor.get_existential_deposit() - assert subtensor.transfer( + existential_deposit = subtensor.chain.get_existential_deposit() + assert subtensor.extrinsics.transfer( wallet=dummy_account_1, dest=dummy_account_2.coldkeypub.ss58_address, amount=None, @@ -83,9 +133,11 @@ def test_transfer_all(subtensor: "Subtensor", alice_wallet): wait_for_inclusion=True, keep_alive=True, ) - balance_after = subtensor.get_balance(dummy_account_1.coldkeypub.ss58_address) + balance_after = subtensor.wallets.get_balance( + dummy_account_1.coldkeypub.ss58_address + ) assert balance_after == existential_deposit - assert subtensor.transfer( + assert subtensor.extrinsics.transfer( wallet=dummy_account_2, dest=alice_wallet.coldkeypub.ss58_address, amount=None, @@ -94,20 +146,26 @@ def test_transfer_all(subtensor: "Subtensor", alice_wallet): wait_for_finalization=True, keep_alive=False, ) - balance_after = subtensor.get_balance(dummy_account_2.coldkeypub.ss58_address) + balance_after = subtensor.wallets.get_balance( + dummy_account_2.coldkeypub.ss58_address + ) assert balance_after == Balance(0) + logging.console.success("✅ Test [green]test_transfer_all[/green] passed.") + @pytest.mark.asyncio -async def test_async_transfer(async_subtensor: "SubtensorApi", alice_wallet): +async def test_transfer_all_async(async_subtensor, alice_wallet): # create two dummy accounts we can drain + logging.console.info("Testing [blue]test_transfer_async[/blue]") + dummy_account_1 = Wallet(path="/tmp/bittensor-dummy-account-3") dummy_account_2 = Wallet(path="/tmp/bittensor-dummy-account-4") dummy_account_1.create_new_coldkey(use_password=False, overwrite=True) dummy_account_2.create_new_coldkey(use_password=False, overwrite=True) # fund the first dummy account - assert await async_subtensor.transfer( + assert await async_subtensor.extrinsics.transfer( alice_wallet, dest=dummy_account_1.coldkeypub.ss58_address, amount=Balance.from_tao(2.0), @@ -115,8 +173,8 @@ async def test_async_transfer(async_subtensor: "SubtensorApi", alice_wallet): wait_for_inclusion=True, ) # Account details before transfer - existential_deposit = await async_subtensor.get_existential_deposit() - assert await async_subtensor.transfer( + existential_deposit = await async_subtensor.chain.get_existential_deposit() + assert await async_subtensor.extrinsics.transfer( wallet=dummy_account_1, dest=dummy_account_2.coldkeypub.ss58_address, amount=None, @@ -125,11 +183,11 @@ async def test_async_transfer(async_subtensor: "SubtensorApi", alice_wallet): wait_for_inclusion=True, keep_alive=True, ) - balance_after = await async_subtensor.get_balance( + balance_after = await async_subtensor.wallets.get_balance( dummy_account_1.coldkeypub.ss58_address ) assert balance_after == existential_deposit - assert await async_subtensor.transfer( + assert await async_subtensor.extrinsics.transfer( wallet=dummy_account_2, dest=alice_wallet.coldkeypub.ss58_address, amount=None, @@ -138,7 +196,9 @@ async def test_async_transfer(async_subtensor: "SubtensorApi", alice_wallet): wait_for_finalization=True, keep_alive=False, ) - balance_after = await async_subtensor.get_balance( + balance_after = await async_subtensor.wallets.get_balance( dummy_account_2.coldkeypub.ss58_address ) assert balance_after == Balance(0) + + logging.console.success("✅ Test [green]test_transfer_async[/green] passed.") From 06b77c6dd3cad1558e207f5af0fbe9932bffdbc0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:55:40 -0700 Subject: [PATCH 46/86] update `commit_weights_extrinsic` extrinsic and subtensor call with `raise_error=True` --- bittensor/core/async_subtensor.py | 1 + bittensor/core/extrinsics/asyncex/weights.py | 7 ++++++- bittensor/core/extrinsics/commit_weights.py | 7 ++++++- bittensor/core/subtensor.py | 1 + 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e40b1f4465..722dcfd7a1 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4569,6 +4569,7 @@ async def commit_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=True, ) if success: break diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index bff8a5096d..33d021abe0 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -23,6 +23,7 @@ async def _do_commit_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, ) -> tuple[bool, str]: """ Internal method to send a transaction to the Bittensor blockchain, committing the hash of a neuron's weights. @@ -38,6 +39,7 @@ async def _do_commit_weights( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + raise_error: raises the relevant exception rather than returning `False` if unsuccessful. Returns: tuple[bool, str]: @@ -64,7 +66,7 @@ async def _do_commit_weights( period=period, nonce_key="hotkey", sign_with="hotkey", - raise_error=True, + raise_error=raise_error, ) @@ -76,6 +78,7 @@ async def commit_weights_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, ) -> tuple[bool, str]: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. @@ -92,6 +95,7 @@ async def commit_weights_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + raise_error: raises the relevant exception rather than returning `False` if unsuccessful. Returns: tuple[bool, str]: @@ -109,6 +113,7 @@ async def commit_weights_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index 3a590d74b6..b5b0a593fa 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -17,6 +17,7 @@ def _do_commit_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = True, ) -> tuple[bool, str]: """ Internal method to send a transaction to the Bittensor blockchain, committing the hash of a neuron's weights. @@ -32,6 +33,7 @@ def _do_commit_weights( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + raise_error: raises the relevant exception rather than returning `True` if unsuccessful. Returns: tuple[bool, str]: @@ -58,7 +60,7 @@ def _do_commit_weights( period=period, sign_with="hotkey", nonce_key="hotkey", - raise_error=True, + raise_error=raise_error, ) @@ -70,6 +72,7 @@ def commit_weights_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, ) -> tuple[bool, str]: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. @@ -85,6 +88,7 @@ def commit_weights_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + raise_error (bool): Whether to raise an error if the transaction fails. Returns: tuple[bool, str]: @@ -103,6 +107,7 @@ def commit_weights_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 28371fb7d6..224f6234a3 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3405,6 +3405,7 @@ def commit_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=True, ) if success: break From bbd9d49be03a7f0831c678e6abbdbff161b7e814 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:55:51 -0700 Subject: [PATCH 47/86] fix unit tests --- tests/unit_tests/extrinsics/asyncex/test_weights.py | 2 ++ tests/unit_tests/extrinsics/test_commit_weights.py | 1 + tests/unit_tests/test_async_subtensor.py | 1 + tests/unit_tests/test_subtensor.py | 1 + 4 files changed, 5 insertions(+) diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 226292b43c..2a00bf51ac 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -446,6 +446,7 @@ async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result is True assert message == "✅ [green]Successfully committed weights.[green]" @@ -481,6 +482,7 @@ async def test_commit_weights_extrinsic_failure(subtensor, fake_wallet, mocker): wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result is False assert message == "Commit failed." diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index e27547528b..4531a34ce0 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -51,6 +51,7 @@ def test_do_commit_weights(subtensor, fake_wallet, mocker): nonce_key="hotkey", sign_with="hotkey", use_nonce=True, + raise_error=True, ) assert result == (False, "") diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index e1bf1420dd..66c0b5dc9b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2942,6 +2942,7 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): wait_for_inclusion=False, wait_for_finalization=False, period=16, + raise_error=True, ) assert result is True assert message == "Success" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 78d9ffeaa9..5ec6a779a3 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1990,6 +1990,7 @@ def test_commit_weights(subtensor, fake_wallet, mocker): wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=16, + raise_error=True, ) assert result == expected_result From 39d8db969cf8873952d36003d9edcce1cb4dda60 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:06:56 -0700 Subject: [PATCH 48/86] remove testing py3.9 since GH actions matrix has a limit as 256 items --- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index d56e9a726d..41226ad48a 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -143,7 +143,7 @@ jobs: os: - ubuntu-latest test: ${{ fromJson(needs.find-tests.outputs.test-files) }} - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Check-out repository uses: actions/checkout@v4 From 2f459c87881dd1d00e013ca681c6fd843892f56a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:12:48 -0700 Subject: [PATCH 49/86] split job for a few ones with different python versions --- .github/workflows/e2e-subtensor-tests.yaml | 251 ++++++++++++++++++++- 1 file changed, 249 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 41226ad48a..703df15104 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -129,7 +129,9 @@ jobs: path: subtensor-localnet.tar # Job to run tests in parallel - run-fast-blocks-e2e-test: + # Since GH Actions matrix has a limit of 256 jobs, we need to split the tests into multiple jobs with different + # Python versions + run-fast-blocks-e2e-test-3.9: name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" needs: - find-tests @@ -143,7 +145,7 @@ jobs: os: - ubuntu-latest test: ${{ fromJson(needs.find-tests.outputs.test-files) }} - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: "3.9" steps: - name: Check-out repository uses: actions/checkout@v4 @@ -189,3 +191,248 @@ jobs: echo "Tests failed after 3 attempts" exit 1 + + run-fast-blocks-e2e-test-3.10: + name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" + needs: + - find-tests + - pull-docker-image + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false # Allow other matrix jobs to run even if this job fails + max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) + matrix: + os: + - ubuntu-latest + test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + python-version: "3.10" + steps: + - name: Check-out repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: install dependencies + run: uv sync --extra dev --dev + + - name: Download Cached Docker Image + uses: actions/download-artifact@v4 + with: + name: subtensor-localnet + + - name: Load Docker Image + run: docker load -i subtensor-localnet.tar + + - name: Run tests with retry + env: + LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} + run: | + for i in 1 2 3; do + echo "::group::🔁 Test attempt $i" + if uv run pytest "${{ matrix.test.nodeid }}" -s; then + echo "✅ Tests passed on attempt $i" + echo "::endgroup::" + exit 0 + else + echo "❌ Tests failed on attempt $i" + echo "::endgroup::" + if [ "$i" -lt 3 ]; then + echo "Retrying..." + sleep 5 + fi + fi + done + + echo "Tests failed after 3 attempts" + exit 1 + + run-fast-blocks-e2e-test-3.11: + name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" + needs: + - find-tests + - pull-docker-image + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false # Allow other matrix jobs to run even if this job fails + max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) + matrix: + os: + - ubuntu-latest + test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + python-version: "3.11" + steps: + - name: Check-out repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: install dependencies + run: uv sync --extra dev --dev + + - name: Download Cached Docker Image + uses: actions/download-artifact@v4 + with: + name: subtensor-localnet + + - name: Load Docker Image + run: docker load -i subtensor-localnet.tar + + - name: Run tests with retry + env: + LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} + run: | + for i in 1 2 3; do + echo "::group::🔁 Test attempt $i" + if uv run pytest "${{ matrix.test.nodeid }}" -s; then + echo "✅ Tests passed on attempt $i" + echo "::endgroup::" + exit 0 + else + echo "❌ Tests failed on attempt $i" + echo "::endgroup::" + if [ "$i" -lt 3 ]; then + echo "Retrying..." + sleep 5 + fi + fi + done + + echo "Tests failed after 3 attempts" + exit 1 + + run-fast-blocks-e2e-test-3.12: + name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" + needs: + - find-tests + - pull-docker-image + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false # Allow other matrix jobs to run even if this job fails + max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) + matrix: + os: + - ubuntu-latest + test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + python-version: "3.12" + steps: + - name: Check-out repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: install dependencies + run: uv sync --extra dev --dev + + - name: Download Cached Docker Image + uses: actions/download-artifact@v4 + with: + name: subtensor-localnet + + - name: Load Docker Image + run: docker load -i subtensor-localnet.tar + + - name: Run tests with retry + env: + LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} + run: | + for i in 1 2 3; do + echo "::group::🔁 Test attempt $i" + if uv run pytest "${{ matrix.test.nodeid }}" -s; then + echo "✅ Tests passed on attempt $i" + echo "::endgroup::" + exit 0 + else + echo "❌ Tests failed on attempt $i" + echo "::endgroup::" + if [ "$i" -lt 3 ]; then + echo "Retrying..." + sleep 5 + fi + fi + done + + echo "Tests failed after 3 attempts" + exit 1 + + run-fast-blocks-e2e-test-3.13: + name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" + needs: + - find-tests + - pull-docker-image + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false # Allow other matrix jobs to run even if this job fails + max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) + matrix: + os: + - ubuntu-latest + test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + python-version: "3.13" + steps: + - name: Check-out repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: install dependencies + run: uv sync --extra dev --dev + + - name: Download Cached Docker Image + uses: actions/download-artifact@v4 + with: + name: subtensor-localnet + + - name: Load Docker Image + run: docker load -i subtensor-localnet.tar + + - name: Run tests with retry + env: + LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} + run: | + for i in 1 2 3; do + echo "::group::🔁 Test attempt $i" + if uv run pytest "${{ matrix.test.nodeid }}" -s; then + echo "✅ Tests passed on attempt $i" + echo "::endgroup::" + exit 0 + else + echo "❌ Tests failed on attempt $i" + echo "::endgroup::" + if [ "$i" -lt 3 ]; then + echo "Retrying..." + sleep 5 + fi + fi + done + + echo "Tests failed after 3 attempts" + exit 1 + From 2c1b81271c516e5fa223cff048d5623d88e589d9 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:26:31 -0700 Subject: [PATCH 50/86] try to add reusable workflow --- .github/workflows/_run-e2e-single.yaml | 74 +++++ .github/workflows/e2e-subtensor-tests.yaml | 357 ++++----------------- 2 files changed, 130 insertions(+), 301 deletions(-) create mode 100644 .github/workflows/_run-e2e-single.yaml diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml new file mode 100644 index 0000000000..55c9192f96 --- /dev/null +++ b/.github/workflows/_run-e2e-single.yaml @@ -0,0 +1,74 @@ +name: Run E2E for single Python version + +on: + workflow_call: + inputs: + python-version: + required: true + type: string + test-files: + required: true + type: string + image-name: + required: true + type: string + + secrets: + GITHUB_TOKEN: + required: true + +jobs: + run-fast-blocks-e2e: + name: "${{ matrix.test.label }} / Py ${{ inputs.python-version }}" + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + max-parallel: 64 + matrix: + test: ${{ fromJson(inputs.test-files) }} + + steps: + - name: Check-out repository + uses: actions/checkout@v4 + + - name: Set up Python ${{ inputs.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install dependencies + run: uv sync --extra dev --dev + + - name: Download Cached Docker Image + uses: actions/download-artifact@v4 + with: + name: subtensor-localnet + + - name: Load Docker Image + run: docker load -i subtensor-localnet.tar + + - name: Run tests with retry + env: + LOCALNET_IMAGE_NAME: ${{ inputs.image-name }} + run: | + for i in 1 2 3; do + echo "::group::🔁 Test attempt $i" + if uv run pytest "${{ matrix.test.nodeid }}" -s; then + echo "✅ Tests passed on attempt $i" + echo "::endgroup::" + exit 0 + else + echo "❌ Tests failed on attempt $i" + echo "::endgroup::" + if [ "$i" -lt 3 ]; then + echo "Retrying..." + sleep 5 + fi + fi + + echo "Tests failed after 3 attempts" + exit 1 diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 703df15104..4b686d31de 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -130,309 +130,64 @@ jobs: # Job to run tests in parallel # Since GH Actions matrix has a limit of 256 jobs, we need to split the tests into multiple jobs with different - # Python versions - run-fast-blocks-e2e-test-3.9: - name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" - needs: - - find-tests - - pull-docker-image - runs-on: ubuntu-latest - timeout-minutes: 45 - strategy: - fail-fast: false # Allow other matrix jobs to run even if this job fails - max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) - matrix: - os: - - ubuntu-latest - test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + # Python versions. To reduce DRY we use reusable workflow. + jobs: + run-e2e-3-9: + name: Run E2E / Python 3.9 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: python-version: "3.9" - steps: - - name: Check-out repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: install dependencies - run: uv sync --extra dev --dev - - - name: Download Cached Docker Image - uses: actions/download-artifact@v4 - with: - name: subtensor-localnet - - - name: Load Docker Image - run: docker load -i subtensor-localnet.tar - - - name: Run tests with retry - env: - LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} - run: | - for i in 1 2 3; do - echo "::group::🔁 Test attempt $i" - if uv run pytest "${{ matrix.test.nodeid }}" -s; then - echo "✅ Tests passed on attempt $i" - echo "::endgroup::" - exit 0 - else - echo "❌ Tests failed on attempt $i" - echo "::endgroup::" - if [ "$i" -lt 3 ]; then - echo "Retrying..." - sleep 5 - fi - fi - done - - echo "Tests failed after 3 attempts" - exit 1 - - run-fast-blocks-e2e-test-3.10: - name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" - needs: - - find-tests - - pull-docker-image - runs-on: ubuntu-latest - timeout-minutes: 45 - strategy: - fail-fast: false # Allow other matrix jobs to run even if this job fails - max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) - matrix: - os: - - ubuntu-latest - test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-10: + name: Run E2E / Python 3.10 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: python-version: "3.10" - steps: - - name: Check-out repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: install dependencies - run: uv sync --extra dev --dev - - - name: Download Cached Docker Image - uses: actions/download-artifact@v4 - with: - name: subtensor-localnet - - - name: Load Docker Image - run: docker load -i subtensor-localnet.tar - - - name: Run tests with retry - env: - LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} - run: | - for i in 1 2 3; do - echo "::group::🔁 Test attempt $i" - if uv run pytest "${{ matrix.test.nodeid }}" -s; then - echo "✅ Tests passed on attempt $i" - echo "::endgroup::" - exit 0 - else - echo "❌ Tests failed on attempt $i" - echo "::endgroup::" - if [ "$i" -lt 3 ]; then - echo "Retrying..." - sleep 5 - fi - fi - done - - echo "Tests failed after 3 attempts" - exit 1 - - run-fast-blocks-e2e-test-3.11: - name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" - needs: - - find-tests - - pull-docker-image - runs-on: ubuntu-latest - timeout-minutes: 45 - strategy: - fail-fast: false # Allow other matrix jobs to run even if this job fails - max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) - matrix: - os: - - ubuntu-latest - test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-11: + name: Run E2E / Python 3.11 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: python-version: "3.11" - steps: - - name: Check-out repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: install dependencies - run: uv sync --extra dev --dev - - - name: Download Cached Docker Image - uses: actions/download-artifact@v4 - with: - name: subtensor-localnet - - - name: Load Docker Image - run: docker load -i subtensor-localnet.tar - - - name: Run tests with retry - env: - LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} - run: | - for i in 1 2 3; do - echo "::group::🔁 Test attempt $i" - if uv run pytest "${{ matrix.test.nodeid }}" -s; then - echo "✅ Tests passed on attempt $i" - echo "::endgroup::" - exit 0 - else - echo "❌ Tests failed on attempt $i" - echo "::endgroup::" - if [ "$i" -lt 3 ]; then - echo "Retrying..." - sleep 5 - fi - fi - done - - echo "Tests failed after 3 attempts" - exit 1 - - run-fast-blocks-e2e-test-3.12: - name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" - needs: - - find-tests - - pull-docker-image - runs-on: ubuntu-latest - timeout-minutes: 45 - strategy: - fail-fast: false # Allow other matrix jobs to run even if this job fails - max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) - matrix: - os: - - ubuntu-latest - test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-12: + name: Run E2E / Python 3.12 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: python-version: "3.12" - steps: - - name: Check-out repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: install dependencies - run: uv sync --extra dev --dev - - - name: Download Cached Docker Image - uses: actions/download-artifact@v4 - with: - name: subtensor-localnet - - - name: Load Docker Image - run: docker load -i subtensor-localnet.tar - - - name: Run tests with retry - env: - LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} - run: | - for i in 1 2 3; do - echo "::group::🔁 Test attempt $i" - if uv run pytest "${{ matrix.test.nodeid }}" -s; then - echo "✅ Tests passed on attempt $i" - echo "::endgroup::" - exit 0 - else - echo "❌ Tests failed on attempt $i" - echo "::endgroup::" - if [ "$i" -lt 3 ]; then - echo "Retrying..." - sleep 5 - fi - fi - done - - echo "Tests failed after 3 attempts" - exit 1 - - run-fast-blocks-e2e-test-3.13: - name: "${{ matrix.test.label }} / Py ${{ matrix.python-version }}" - needs: - - find-tests - - pull-docker-image - runs-on: ubuntu-latest - timeout-minutes: 45 - strategy: - fail-fast: false # Allow other matrix jobs to run even if this job fails - max-parallel: 64 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) - matrix: - os: - - ubuntu-latest - test: ${{ fromJson(needs.find-tests.outputs.test-files) }} + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-13: + name: Run E2E / Python 3.13 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: python-version: "3.13" - steps: - - name: Check-out repository - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: install dependencies - run: uv sync --extra dev --dev - - - name: Download Cached Docker Image - uses: actions/download-artifact@v4 - with: - name: subtensor-localnet - - - name: Load Docker Image - run: docker load -i subtensor-localnet.tar - - - name: Run tests with retry - env: - LOCALNET_IMAGE_NAME: ${{ needs.pull-docker-image.outputs.image-name }} - run: | - for i in 1 2 3; do - echo "::group::🔁 Test attempt $i" - if uv run pytest "${{ matrix.test.nodeid }}" -s; then - echo "✅ Tests passed on attempt $i" - echo "::endgroup::" - exit 0 - else - echo "❌ Tests failed on attempt $i" - echo "::endgroup::" - if [ "$i" -lt 3 ]; then - echo "Retrying..." - sleep 5 - fi - fi - done - - echo "Tests failed after 3 attempts" - exit 1 - + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit From c45d05c1dd2c4254ae67e9bbbe4fe332165b5043 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:31:54 -0700 Subject: [PATCH 51/86] indent fix --- .github/workflows/e2e-subtensor-tests.yaml | 120 ++++++++++----------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 4b686d31de..a07a8e9c50 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -131,63 +131,63 @@ jobs: # Job to run tests in parallel # Since GH Actions matrix has a limit of 256 jobs, we need to split the tests into multiple jobs with different # Python versions. To reduce DRY we use reusable workflow. - jobs: - run-e2e-3-9: - name: Run E2E / Python 3.9 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.9" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-10: - name: Run E2E / Python 3.10 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.10" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-11: - name: Run E2E / Python 3.11 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.11" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-12: - name: Run E2E / Python 3.12 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.12" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-13: - name: Run E2E / Python 3.13 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.13" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit + + run-e2e-3-9: + name: Run E2E / Python 3.9 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: + python-version: "3.9" + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-10: + name: Run E2E / Python 3.10 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: + python-version: "3.10" + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-11: + name: Run E2E / Python 3.11 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: + python-version: "3.11" + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-12: + name: Run E2E / Python 3.12 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: + python-version: "3.12" + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + + run-e2e-3-13: + name: Run E2E / Python 3.13 + uses: ./.github/workflows/_run-e2e-single.yaml + needs: + - find-tests + - pull-docker-image + with: + python-version: "3.13" + test-files: ${{ needs.find-tests.outputs.test-files }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit From 72847c54d4bfadf218a41217ca2c071dddf9a39c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:33:42 -0700 Subject: [PATCH 52/86] remove secrets from reusable workflow --- .github/workflows/_run-e2e-single.yaml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 55c9192f96..9a4d8aac93 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -13,10 +13,6 @@ on: required: true type: string - secrets: - GITHUB_TOKEN: - required: true - jobs: run-fast-blocks-e2e: name: "${{ matrix.test.label }} / Py ${{ inputs.python-version }}" From 1de47ecdf4d79346aa4ae7742212b8e51fd99577 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:40:57 -0700 Subject: [PATCH 53/86] done back --- .github/workflows/_run-e2e-single.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 9a4d8aac93..7f9a9b5e34 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -66,5 +66,6 @@ jobs: fi fi + done echo "Tests failed after 3 attempts" exit 1 From 7ab9c2f11e509803031f0a1c2eecf8fe698c94ce Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:44:53 -0700 Subject: [PATCH 54/86] names --- .github/workflows/_run-e2e-single.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 7f9a9b5e34..35db82e44c 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -22,7 +22,7 @@ jobs: fail-fast: false max-parallel: 64 matrix: - test: ${{ fromJson(inputs.test-files) }} + include: ${{ fromJson(inputs.test-files) }} steps: - name: Check-out repository From 4eb9a9b5d2d302548e27077e9308dce8bd90e46c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:49:23 -0700 Subject: [PATCH 55/86] names --- .github/workflows/_run-e2e-single.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 35db82e44c..6eee9ec04d 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -22,7 +22,8 @@ jobs: fail-fast: false max-parallel: 64 matrix: - include: ${{ fromJson(inputs.test-files) }} + name: ${{ fromJson(inputs.test-files) }} + test: ${{ fromJson(inputs.test-files) }} steps: - name: Check-out repository From 43ee5d98cb6d9e4ab566396171d024c4c5682331 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:54:47 -0700 Subject: [PATCH 56/86] names --- .github/workflows/_run-e2e-single.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 6eee9ec04d..82315624b0 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -15,14 +15,13 @@ on: jobs: run-fast-blocks-e2e: - name: "${{ matrix.test.label }} / Py ${{ inputs.python-version }}" runs-on: ubuntu-latest timeout-minutes: 45 strategy: fail-fast: false max-parallel: 64 matrix: - name: ${{ fromJson(inputs.test-files) }} + name: "${{ matrix.test.label }} / Py ${{ inputs.python-version }}" test: ${{ fromJson(inputs.test-files) }} steps: From 8da133158add8d285cd1da7b8904ffd468c0901a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 19:03:51 -0700 Subject: [PATCH 57/86] names again --- .github/workflows/_run-e2e-single.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 82315624b0..7f9a9b5e34 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -15,13 +15,13 @@ on: jobs: run-fast-blocks-e2e: + name: "${{ matrix.test.label }} / Py ${{ inputs.python-version }}" runs-on: ubuntu-latest timeout-minutes: 45 strategy: fail-fast: false max-parallel: 64 matrix: - name: "${{ matrix.test.label }} / Py ${{ inputs.python-version }}" test: ${{ fromJson(inputs.test-files) }} steps: From 5e7d144f97ebe3438eec91e2bb0fa258ddb89e07 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 19:53:03 -0700 Subject: [PATCH 58/86] convert `async_subtensor` fixture to real async fixture --- tests/e2e_tests/conftest.py | 19 +++++++++++++++---- tests/pytest.ini | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 2ca74c2063..1c2f8a42e3 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -9,6 +9,7 @@ import time import pytest +import pytest_asyncio from async_substrate_interface import SubstrateInterface from bittensor.core.subtensor_api import SubtensorApi @@ -269,11 +270,21 @@ def subtensor(local_chain): return SubtensorApi(network="ws://localhost:9944", legacy_methods=False) -@pytest.fixture -def async_subtensor(local_chain): - return SubtensorApi( +# @pytest.fixture +# def async_subtensor(local_chain): +# a_sub = SubtensorApi( +# network="ws://localhost:9944", legacy_methods=False, async_subtensor=True +# ) +# a_sub.initialize() +# return a_sub + + +@pytest_asyncio.fixture +async def async_subtensor(local_chain): + async with SubtensorApi( network="ws://localhost:9944", legacy_methods=False, async_subtensor=True - ) + ) as a_sub: + return a_sub @pytest.fixture diff --git a/tests/pytest.ini b/tests/pytest.ini index 299f47bd6e..42fa0889f5 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,3 +1,3 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_fixture_loop_scope = "session" \ No newline at end of file +asyncio_default_fixture_loop_scope = session \ No newline at end of file From 96f29689d9f508c266c9c14f00b7ac7ba8690c75 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:05:37 -0700 Subject: [PATCH 59/86] testing update for main and reusable workflows --- .github/workflows/_run-e2e-single.yaml | 22 ++-- .github/workflows/e2e-subtensor-tests.yaml | 117 +++++++++++---------- 2 files changed, 72 insertions(+), 67 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 7f9a9b5e34..a75027acc8 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -1,4 +1,4 @@ -name: Run E2E for single Python version +name: Run Single E2E Test on: workflow_call: @@ -6,7 +6,10 @@ on: python-version: required: true type: string - test-files: + nodeid: + required: true + type: string + label: required: true type: string image-name: @@ -14,15 +17,10 @@ on: type: string jobs: - run-fast-blocks-e2e: - name: "${{ matrix.test.label }} / Py ${{ inputs.python-version }}" + run-e2e: + name: "${{ inputs.label }} / Py ${{ inputs.python-version }}" runs-on: ubuntu-latest timeout-minutes: 45 - strategy: - fail-fast: false - max-parallel: 64 - matrix: - test: ${{ fromJson(inputs.test-files) }} steps: - name: Check-out repository @@ -47,13 +45,13 @@ jobs: - name: Load Docker Image run: docker load -i subtensor-localnet.tar - - name: Run tests with retry + - name: Run test with retry env: LOCALNET_IMAGE_NAME: ${{ inputs.image-name }} run: | for i in 1 2 3; do echo "::group::🔁 Test attempt $i" - if uv run pytest "${{ matrix.test.nodeid }}" -s; then + if uv run pytest "${{ inputs.nodeid }}" -s; then echo "✅ Tests passed on attempt $i" echo "::endgroup::" exit 0 @@ -68,4 +66,4 @@ jobs: done echo "Tests failed after 3 attempts" - exit 1 + exit 1 \ No newline at end of file diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index a07a8e9c50..13a2b8a1fd 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -133,61 +133,68 @@ jobs: # Python versions. To reduce DRY we use reusable workflow. run-e2e-3-9: - name: Run E2E / Python 3.9 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.9" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-10: - name: Run E2E / Python 3.10 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.10" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-11: - name: Run E2E / Python 3.11 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.11" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-12: - name: Run E2E / Python 3.12 - uses: ./.github/workflows/_run-e2e-single.yaml - needs: - - find-tests - - pull-docker-image - with: - python-version: "3.12" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit - - run-e2e-3-13: - name: Run E2E / Python 3.13 - uses: ./.github/workflows/_run-e2e-single.yaml + name: Run E2E / Py 3.9 - ${{ matrix.label }} + runs-on: ubuntu-latest needs: - find-tests - pull-docker-image - with: - python-version: "3.13" - test-files: ${{ needs.find-tests.outputs.test-files }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit + strategy: + fail-fast: false + matrix: + include: ${{ fromJson(needs.find-tests.outputs.test-files) }} + steps: + - uses: ./.github/workflows/_run-e2e-single.yaml + with: + python-version: "3.9" + nodeid: ${{ matrix.nodeid }} + label: ${{ matrix.label }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} + secrets: inherit + +# run-e2e-3-10: +# name: Run E2E / Python 3.10 +# uses: ./.github/workflows/_run-e2e-single.yaml +# needs: +# - find-tests +# - pull-docker-image +# with: +# python-version: "3.10" +# test-files: ${{ needs.find-tests.outputs.test-files }} +# image-name: ${{ needs.pull-docker-image.outputs.image-name }} +# secrets: inherit +# +# run-e2e-3-11: +# name: Run E2E / Python 3.11 +# uses: ./.github/workflows/_run-e2e-single.yaml +# needs: +# - find-tests +# - pull-docker-image +# with: +# python-version: "3.11" +# test-files: ${{ needs.find-tests.outputs.test-files }} +# image-name: ${{ needs.pull-docker-image.outputs.image-name }} +# secrets: inherit +# +# run-e2e-3-12: +# name: Run E2E / Python 3.12 +# uses: ./.github/workflows/_run-e2e-single.yaml +# needs: +# - find-tests +# - pull-docker-image +# with: +# python-version: "3.12" +# test-files: ${{ needs.find-tests.outputs.test-files }} +# image-name: ${{ needs.pull-docker-image.outputs.image-name }} +# secrets: inherit +# +# run-e2e-3-13: +# name: Run E2E / Python 3.13 +# uses: ./.github/workflows/_run-e2e-single.yaml +# needs: +# - find-tests +# - pull-docker-image +# with: +# python-version: "3.13" +# test-files: ${{ needs.find-tests.outputs.test-files }} +# image-name: ${{ needs.pull-docker-image.outputs.image-name }} +# secrets: inherit From a4ff7705e5d9c2e3469ee1de932c69aa5afddb31 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:07:34 -0700 Subject: [PATCH 60/86] secret --- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 13a2b8a1fd..c290182570 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -149,7 +149,7 @@ jobs: nodeid: ${{ matrix.nodeid }} label: ${{ matrix.label }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} - secrets: inherit + secrets: inherit # run-e2e-3-10: # name: Run E2E / Python 3.10 From fb1a4a75e77a06aa032690b6d5c37258a438cb48 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:09:29 -0700 Subject: [PATCH 61/86] run-e2e-3-9 --- .github/workflows/e2e-subtensor-tests.yaml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index c290182570..f8d781e518 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -134,7 +134,6 @@ jobs: run-e2e-3-9: name: Run E2E / Py 3.9 - ${{ matrix.label }} - runs-on: ubuntu-latest needs: - find-tests - pull-docker-image @@ -142,13 +141,12 @@ jobs: fail-fast: false matrix: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} - steps: - - uses: ./.github/workflows/_run-e2e-single.yaml - with: - python-version: "3.9" - nodeid: ${{ matrix.nodeid }} - label: ${{ matrix.label }} - image-name: ${{ needs.pull-docker-image.outputs.image-name }} + uses: ./.github/workflows/_run-e2e-single.yaml + with: + python-version: "3.9" + nodeid: ${{ matrix.nodeid }} + label: ${{ matrix.label }} + image-name: ${{ needs.pull-docker-image.outputs.image-name }} secrets: inherit # run-e2e-3-10: From 575fa0d419ee0502ff6ba8782022ff801cb621ad Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:16:32 -0700 Subject: [PATCH 62/86] run-e2e-3-9 --- .github/workflows/_run-e2e-single.yaml | 2 +- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index a75027acc8..785a14ef2f 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -18,7 +18,7 @@ on: jobs: run-e2e: - name: "${{ inputs.label }} / Py ${{ inputs.python-version }}" +# name: "${{ inputs.label }} / Py ${{ inputs.python-version }}" runs-on: ubuntu-latest timeout-minutes: 45 diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index f8d781e518..5616ebc43a 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -133,7 +133,7 @@ jobs: # Python versions. To reduce DRY we use reusable workflow. run-e2e-3-9: - name: Run E2E / Py 3.9 - ${{ matrix.label }} + name: ${{ matrix.label }} / Python 3.9 needs: - find-tests - pull-docker-image From ec8f4abda5d9906a505b54686c0d8900111ac8d2 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:17:51 -0700 Subject: [PATCH 63/86] remove needs from pull-docker-image (save time) --- .github/workflows/e2e-subtensor-tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 5616ebc43a..6397c56fd2 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -63,7 +63,6 @@ jobs: # Pull docker image pull-docker-image: - needs: find-tests runs-on: ubuntu-latest outputs: image-name: ${{ steps.set-image.outputs.image }} From f8c7690418c2e9ee8e737aa0824b75711ec4b299 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:22:54 -0700 Subject: [PATCH 64/86] try --- .github/workflows/_run-e2e-single.yaml | 2 +- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 785a14ef2f..a75027acc8 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -18,7 +18,7 @@ on: jobs: run-e2e: -# name: "${{ inputs.label }} / Py ${{ inputs.python-version }}" + name: "${{ inputs.label }} / Py ${{ inputs.python-version }}" runs-on: ubuntu-latest timeout-minutes: 45 diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 6397c56fd2..2aaaf81ad2 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -144,7 +144,7 @@ jobs: with: python-version: "3.9" nodeid: ${{ matrix.nodeid }} - label: ${{ matrix.label }} + label: ${{ name }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} secrets: inherit From 1e3082c716ea7b4590c22a0ee5ec56b1f3eeafc2 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:23:27 -0700 Subject: [PATCH 65/86] try --- .github/workflows/e2e-subtensor-tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 2aaaf81ad2..e719629f53 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -132,7 +132,7 @@ jobs: # Python versions. To reduce DRY we use reusable workflow. run-e2e-3-9: - name: ${{ matrix.label }} / Python 3.9 +# name: ${{ matrix.label }} / Python 3.9 needs: - find-tests - pull-docker-image @@ -144,7 +144,7 @@ jobs: with: python-version: "3.9" nodeid: ${{ matrix.nodeid }} - label: ${{ name }} + label: ${{ matrix.label }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} secrets: inherit From 3df041e15dfb8a207020d0f0bc5187e23583e533 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:34:43 -0700 Subject: [PATCH 66/86] matrix.label --- .github/workflows/e2e-subtensor-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index e719629f53..bf7856ee40 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -132,7 +132,7 @@ jobs: # Python versions. To reduce DRY we use reusable workflow. run-e2e-3-9: -# name: ${{ matrix.label }} / Python 3.9 + name: ${{ matrix.label }} needs: - find-tests - pull-docker-image From 976ebf72e04d7521e162904d0fd56f836ba317e0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:40:43 -0700 Subject: [PATCH 67/86] multy matrix --- .github/workflows/_run-e2e-single.yaml | 17 ++++++++--------- .github/workflows/e2e-subtensor-tests.yaml | 2 -- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index a75027acc8..e975f2d6fb 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -3,33 +3,32 @@ name: Run Single E2E Test on: workflow_call: inputs: - python-version: - required: true - type: string nodeid: required: true type: string - label: - required: true - type: string image-name: required: true type: string jobs: run-e2e: - name: "${{ inputs.label }} / Py ${{ inputs.python-version }}" + name: "${{ matrix.python-version }} / Py ${{ inputs.python-version }}" runs-on: ubuntu-latest timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + steps: - name: Check-out repository uses: actions/checkout@v4 - - name: Set up Python ${{ inputs.python-version }} + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: - python-version: ${{ inputs.python-version }} + python-version: ${{ matrix.python-version }} - name: Install uv uses: astral-sh/setup-uv@v4 diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index bf7856ee40..5a64e5eac5 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -142,9 +142,7 @@ jobs: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} uses: ./.github/workflows/_run-e2e-single.yaml with: - python-version: "3.9" nodeid: ${{ matrix.nodeid }} - label: ${{ matrix.label }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} secrets: inherit From 602a176f8e336a4b124c3cfd4fcbd830d50e56c5 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:46:05 -0700 Subject: [PATCH 68/86] multy matrix py --- .github/workflows/_run-e2e-single.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index e975f2d6fb..3768fc8270 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -12,7 +12,7 @@ on: jobs: run-e2e: - name: "${{ matrix.python-version }} / Py ${{ inputs.python-version }}" + name: "${{ matrix.python-version }}" runs-on: ubuntu-latest timeout-minutes: 45 From 32a9109805b13be29ebf1586196a5ee3d21b8c43 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:03:39 -0700 Subject: [PATCH 69/86] avoid uv loop issue with python 3.9 --- .github/workflows/_run-e2e-single.yaml | 33 ++++++++++++++++++-------- tests/pytest.ini | 3 ++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 3768fc8270..8f005b4e50 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -46,23 +46,36 @@ jobs: - name: Run test with retry env: + PY_VERSION: ${{ matrix.python-version }} LOCALNET_IMAGE_NAME: ${{ inputs.image-name }} run: | for i in 1 2 3; do echo "::group::🔁 Test attempt $i" - if uv run pytest "${{ inputs.nodeid }}" -s; then - echo "✅ Tests passed on attempt $i" - echo "::endgroup::" - exit 0 + + if [[ "PY_VERSION" == "3.9" ]]; then + echo "🔧 Running plain pytest (no uv)" + if pytest "${{ inputs.nodeid }}" -s; then + echo "✅ Passed on attempt $i" + echo "::endgroup::" + exit 0 + fi else - echo "❌ Tests failed on attempt $i" - echo "::endgroup::" - if [ "$i" -lt 3 ]; then - echo "Retrying..." - sleep 5 + echo "🚀 Running uv run pytest" + if uv run pytest "${{ inputs.nodeid }}" -s; then + echo "✅ Passed on attempt $i" + echo "::endgroup::" + exit 0 fi fi - + + echo "❌ Failed on attempt $i" + echo "::endgroup::" + + if [ "$i" -lt 3 ]; then + echo "Retrying..." + sleep 5 + fi done + echo "Tests failed after 3 attempts" exit 1 \ No newline at end of file diff --git a/tests/pytest.ini b/tests/pytest.ini index 42fa0889f5..debb3c74c5 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,3 +1,4 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_fixture_loop_scope = session \ No newline at end of file +asyncio_default_fixture_loop_scope = session +asyncio_mode = auto From 3049701efd7dd37c1743732189128f72d262a75b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:15:05 -0700 Subject: [PATCH 70/86] remove commented code from workflow, rollback uv run --- .github/workflows/_run-e2e-single.yaml | 34 +++++---------- .github/workflows/e2e-subtensor-tests.yaml | 50 +--------------------- tests/pytest.ini | 1 - 3 files changed, 12 insertions(+), 73 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 8f005b4e50..3464d8d1a3 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -1,4 +1,4 @@ -name: Run Single E2E Test +name: Run Single E2E Test with multiple Python versions on: workflow_call: @@ -51,31 +51,19 @@ jobs: run: | for i in 1 2 3; do echo "::group::🔁 Test attempt $i" - - if [[ "PY_VERSION" == "3.9" ]]; then - echo "🔧 Running plain pytest (no uv)" - if pytest "${{ inputs.nodeid }}" -s; then - echo "✅ Passed on attempt $i" - echo "::endgroup::" - exit 0 - fi + if uv run pytest "${{ inputs.nodeid }}" -s; then + echo "✅ Tests passed on attempt $i" + echo "::endgroup::" + exit 0 else - echo "🚀 Running uv run pytest" - if uv run pytest "${{ inputs.nodeid }}" -s; then - echo "✅ Passed on attempt $i" - echo "::endgroup::" - exit 0 + echo "❌ Tests failed on attempt $i" + echo "::endgroup::" + if [ "$i" -lt 3 ]; then + echo "Retrying..." + sleep 5 fi fi - - echo "❌ Failed on attempt $i" - echo "::endgroup::" - - if [ "$i" -lt 3 ]; then - echo "Retrying..." - sleep 5 - fi - done + done echo "Tests failed after 3 attempts" exit 1 \ No newline at end of file diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 5a64e5eac5..dd819957f4 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -131,7 +131,7 @@ jobs: # Since GH Actions matrix has a limit of 256 jobs, we need to split the tests into multiple jobs with different # Python versions. To reduce DRY we use reusable workflow. - run-e2e-3-9: + e2e-test: name: ${{ matrix.label }} needs: - find-tests @@ -145,51 +145,3 @@ jobs: nodeid: ${{ matrix.nodeid }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} secrets: inherit - -# run-e2e-3-10: -# name: Run E2E / Python 3.10 -# uses: ./.github/workflows/_run-e2e-single.yaml -# needs: -# - find-tests -# - pull-docker-image -# with: -# python-version: "3.10" -# test-files: ${{ needs.find-tests.outputs.test-files }} -# image-name: ${{ needs.pull-docker-image.outputs.image-name }} -# secrets: inherit -# -# run-e2e-3-11: -# name: Run E2E / Python 3.11 -# uses: ./.github/workflows/_run-e2e-single.yaml -# needs: -# - find-tests -# - pull-docker-image -# with: -# python-version: "3.11" -# test-files: ${{ needs.find-tests.outputs.test-files }} -# image-name: ${{ needs.pull-docker-image.outputs.image-name }} -# secrets: inherit -# -# run-e2e-3-12: -# name: Run E2E / Python 3.12 -# uses: ./.github/workflows/_run-e2e-single.yaml -# needs: -# - find-tests -# - pull-docker-image -# with: -# python-version: "3.12" -# test-files: ${{ needs.find-tests.outputs.test-files }} -# image-name: ${{ needs.pull-docker-image.outputs.image-name }} -# secrets: inherit -# -# run-e2e-3-13: -# name: Run E2E / Python 3.13 -# uses: ./.github/workflows/_run-e2e-single.yaml -# needs: -# - find-tests -# - pull-docker-image -# with: -# python-version: "3.13" -# test-files: ${{ needs.find-tests.outputs.test-files }} -# image-name: ${{ needs.pull-docker-image.outputs.image-name }} -# secrets: inherit diff --git a/tests/pytest.ini b/tests/pytest.ini index debb3c74c5..4eb7bcd8e1 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,4 +1,3 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: asyncio_default_fixture_loop_scope = session -asyncio_mode = auto From d3e1e01b748321f4ae186f95c070df3dfe89d154 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:28:48 -0700 Subject: [PATCH 71/86] try to handle loop issue --- tests/pytest.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/pytest.ini b/tests/pytest.ini index 4eb7bcd8e1..3f620124d7 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,3 +1,5 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_fixture_loop_scope = session +asyncio_default_fixture_loop_scope = module +asyncio_mode = strict +asyncio_default_test_loop_scope = module From a1d15fc81029920447360edef691666b39894822 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:40:59 -0700 Subject: [PATCH 72/86] pytest.ini update --- .github/workflows/unit-and-integration-tests.yml | 2 +- tests/pytest.ini | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/unit-and-integration-tests.yml b/.github/workflows/unit-and-integration-tests.yml index ec1fc6ded0..e0129c1602 100644 --- a/.github/workflows/unit-and-integration-tests.yml +++ b/.github/workflows/unit-and-integration-tests.yml @@ -57,4 +57,4 @@ jobs: PYTHONUNBUFFERED: "1" run: | source venv/bin/activate - python -m uv run pytest -n 2 tests/integration_tests/ --reruns 3 + python -m uv run pytest tests/integration_tests/ --reruns 3 diff --git a/tests/pytest.ini b/tests/pytest.ini index 3f620124d7..3cd2886d39 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,5 +1,5 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_fixture_loop_scope = module -asyncio_mode = strict -asyncio_default_test_loop_scope = module +asyncio_default_fixture_loop_scope = session +asyncio_default_test_loop_scope = session +asyncio_mode = auto From 55425f9d117a5b57381f217cf640f866307fa2b2 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:47:32 -0700 Subject: [PATCH 73/86] pytest.ini update + integration --- .github/workflows/unit-and-integration-tests.yml | 2 +- tests/pytest.ini | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/unit-and-integration-tests.yml b/.github/workflows/unit-and-integration-tests.yml index e0129c1602..89089c3f60 100644 --- a/.github/workflows/unit-and-integration-tests.yml +++ b/.github/workflows/unit-and-integration-tests.yml @@ -57,4 +57,4 @@ jobs: PYTHONUNBUFFERED: "1" run: | source venv/bin/activate - python -m uv run pytest tests/integration_tests/ --reruns 3 + python -m uv run pytest -n 2 tests/integration_tests/ --reruns 3 \ No newline at end of file diff --git a/tests/pytest.ini b/tests/pytest.ini index 3cd2886d39..5f8efb479b 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,5 +1,4 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_fixture_loop_scope = session asyncio_default_test_loop_scope = session asyncio_mode = auto From 3525eb808437cffc7126b861265315c67276169c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:49:41 -0700 Subject: [PATCH 74/86] pytest.ini --- tests/pytest.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/pytest.ini b/tests/pytest.ini index 5f8efb479b..597cfcf773 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,4 +1,2 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_test_loop_scope = session -asyncio_mode = auto From 9f2e9d10922439d51be45779591ed5d8f27f0fcf Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:07:24 -0700 Subject: [PATCH 75/86] set raise_error=True for `reveal_weights` --- bittensor/core/async_subtensor.py | 1 + bittensor/core/extrinsics/asyncex/weights.py | 6 ++++++ bittensor/core/extrinsics/commit_weights.py | 5 +++++ bittensor/core/subtensor.py | 1 + 4 files changed, 13 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 722dcfd7a1..3fcdc3ff68 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4897,6 +4897,7 @@ async def reveal_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=True, ) if success: break diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 33d021abe0..6d06210702 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -136,6 +136,7 @@ async def _do_reveal_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, ) -> tuple[bool, str]: """ Internal method to send a transaction to the Bittensor blockchain, revealing the weights for a specific subnet. @@ -155,6 +156,7 @@ async def _do_reveal_weights( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + raise_error: raises the relevant exception rather than returning `False` if unsuccessful. Returns: tuple[bool, str]: @@ -184,6 +186,7 @@ async def _do_reveal_weights( period=period, nonce_key="hotkey", use_nonce=True, + raise_error=raise_error, ) @@ -198,6 +201,7 @@ async def reveal_weights_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -216,6 +220,7 @@ async def reveal_weights_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + raise_error: raises the relevant exception rather than returning `False` if unsuccessful. Returns: tuple[bool, str]: @@ -236,6 +241,7 @@ async def reveal_weights_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index b5b0a593fa..00b3e4cb6c 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -130,6 +130,7 @@ def _do_reveal_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, ) -> tuple[bool, str]: """ Internal method to send a transaction to the Bittensor blockchain, revealing the weights for a specific subnet. @@ -178,6 +179,7 @@ def _do_reveal_weights( period=period, sign_with="hotkey", nonce_key="hotkey", + raise_error=raise_error, ) @@ -192,6 +194,7 @@ def reveal_weights_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -210,6 +213,7 @@ def reveal_weights_extrinsic( period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted. If the transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. + raise_error: raises the relevant exception rather than returning `False` if unsuccessful. Returns: tuple[bool, str]: @@ -231,6 +235,7 @@ def reveal_weights_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 224f6234a3..e27963947a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3732,6 +3732,7 @@ def reveal_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=True, ) if success: break From 91ca87c554ff0bafc762e9916011e0189c471ed6 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:14:09 -0700 Subject: [PATCH 76/86] unit test + pytest.ini --- tests/pytest.ini | 1 + tests/unit_tests/test_subtensor.py | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/pytest.ini b/tests/pytest.ini index 597cfcf773..299f47bd6e 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,2 +1,3 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: +asyncio_default_fixture_loop_scope = "session" \ No newline at end of file diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 5ec6a779a3..5d456456c4 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2031,6 +2031,7 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): wait_for_inclusion=False, wait_for_finalization=False, period=16, + raise_error=True, ) From edf96e5db9a8dc70cfddbb8a406acb8a2a2ee9af Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:33:56 -0700 Subject: [PATCH 77/86] `functools.partial` + `asyncio.gather` breaks the loop for py3.9-11. But works for 3.12-13 --- bittensor/core/async_subtensor.py | 34 ++++++++++++++++--------------- tests/pytest.ini | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 3fcdc3ff68..1b9edf22e7 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2,7 +2,6 @@ import copy import ssl from datetime import datetime, timezone -from functools import partial from typing import cast, Optional, Any, Union, Iterable, TYPE_CHECKING import asyncstdlib as a @@ -2579,24 +2578,27 @@ async def get_stake( Balance: The stake under the coldkey - hotkey pairing. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - sub_query = partial( - self.query_subtensor, + + alpha_shares = await self.query_subtensor( + name="Alpha", + block=block, block_hash=block_hash, reuse_block=reuse_block, + params=[hotkey_ss58, coldkey_ss58, netuid], ) - alpha_shares, hotkey_alpha_result, hotkey_shares = await asyncio.gather( - sub_query( - name="Alpha", - params=[hotkey_ss58, coldkey_ss58, netuid], - ), - sub_query( - name="TotalHotkeyAlpha", - params=[hotkey_ss58, netuid], - ), - sub_query( - name="TotalHotkeyShares", - params=[hotkey_ss58, netuid], - ), + hotkey_alpha_result = await self.query_subtensor( + name="TotalHotkeyAlpha", + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + params=[hotkey_ss58, netuid], + ) + hotkey_shares = await self.query_subtensor( + name="TotalHotkeyShares", + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + params=[hotkey_ss58, netuid], ) hotkey_alpha: int = getattr(hotkey_alpha_result, "value", 0) diff --git a/tests/pytest.ini b/tests/pytest.ini index 299f47bd6e..42fa0889f5 100644 --- a/tests/pytest.ini +++ b/tests/pytest.ini @@ -1,3 +1,3 @@ [pytest] filterwarnings = ignore::DeprecationWarning:pkg_resources.*: -asyncio_default_fixture_loop_scope = "session" \ No newline at end of file +asyncio_default_fixture_loop_scope = session \ No newline at end of file From 879df0eff51568c254086571a39d5ae8c565fc65 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:46:34 -0700 Subject: [PATCH 78/86] add powerful runner for child workflow --- .github/workflows/_run-e2e-single.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 3464d8d1a3..c086cc4102 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -13,7 +13,7 @@ on: jobs: run-e2e: name: "${{ matrix.python-version }}" - runs-on: ubuntu-latest + runs-on: [SubtensorCI] timeout-minutes: 45 strategy: From d9d1067e9d6d4f812cc8678a6c56bff69cf375d2 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 04:01:39 -0700 Subject: [PATCH 79/86] use both runners --- .github/workflows/_run-e2e-single.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index c086cc4102..482550b5d1 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -13,7 +13,7 @@ on: jobs: run-e2e: name: "${{ matrix.python-version }}" - runs-on: [SubtensorCI] + runs-on: [ubuntu-latest, SubtensorCI] timeout-minutes: 45 strategy: From c9172c313f27e3845acb4ab8ad979d09071a23eb Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 04:05:48 -0700 Subject: [PATCH 80/86] nope --- .github/workflows/_run-e2e-single.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 482550b5d1..3464d8d1a3 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -13,7 +13,7 @@ on: jobs: run-e2e: name: "${{ matrix.python-version }}" - runs-on: [ubuntu-latest, SubtensorCI] + runs-on: ubuntu-latest timeout-minutes: 45 strategy: From 834e1970d2410b7941c63ffa32f28c7840c6a8e6 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 11:18:47 -0700 Subject: [PATCH 81/86] `asyncio.gather` causes ASI to return a `RuntimeError` when calling again instead of the expected response. --- bittensor/core/async_subtensor.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1b9edf22e7..1294c4a8f9 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3907,20 +3907,17 @@ async def subnet( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - query, price = await asyncio.gather( - self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_dynamic_info", - params=[netuid], - block_hash=block_hash, - ), - self.get_subnet_price( - netuid=netuid, - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ), - return_exceptions=True, + query = await self.substrate.runtime_call( + "SubnetInfoRuntimeApi", + "get_dynamic_info", + params=[netuid], + block_hash=block_hash, + ) + price = await self.get_subnet_price( + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, ) if isinstance(decoded := query.decode(), dict): From 41088c2904e18e4332c9b019a83ba40973c750cb Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 11:35:42 -0700 Subject: [PATCH 82/86] update tests --- tests/e2e_tests/test_commit_reveal.py | 12 +++--- tests/e2e_tests/test_commit_weights.py | 40 +++++++++---------- .../test_cross_subtensor_compatibility.py | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 197a45e02f..0fc4637359 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -7,6 +7,8 @@ from bittensor.utils.weight_utils import convert_weights_and_uids_for_emit from tests.e2e_tests.utils.chain_interactions import ( async_wait_interval, + async_sudo_set_admin_utils, + async_sudo_set_hyperparameter_bool, sudo_set_admin_utils, sudo_set_hyperparameter_bool, wait_interval, @@ -233,7 +235,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle @pytest.mark.asyncio async def test_commit_and_reveal_weights_cr4_async( - local_chain, async_subtensor, alice_wallet + async_subtensor, alice_wallet, local_chain ): """ Tests the commit/reveal weights mechanism (CR3) @@ -257,7 +259,7 @@ async def test_commit_and_reveal_weights_cr4_async( (0.25, 100) if await async_subtensor.chain.is_fast_blocks() else (12.0, 20) ) - logging.console.info(f"Using block time: {BLOCK_TIME}") + logging.console.info(f"Using block time: {BLOCK_TIME} seconds.") alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 @@ -274,8 +276,8 @@ async def test_commit_and_reveal_weights_cr4_async( logging.console.success(f"SN #{alice_subnet_netuid} is registered.") # Enable commit_reveal on the subnet - assert sudo_set_hyperparameter_bool( - substrate=local_chain, + assert await async_sudo_set_hyperparameter_bool( + substrate=async_subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_commit_reveal_weights_enabled", value=True, @@ -370,8 +372,6 @@ async def test_commit_and_reveal_weights_cr4_async( f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" ) - # commit_block is the block when weights were committed on the chain (transaction block) - expected_commit_block = await async_subtensor.block + 1 # Commit weights success, message = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index c219e63b96..2eb288cd14 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -17,7 +17,7 @@ @pytest.mark.asyncio -async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wallet): +async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): """ Tests the commit/reveal weights mechanism with subprocess disabled (CR1.0) @@ -45,11 +45,11 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - local_chain, - alice_wallet, - "sudo_set_commit_reveal_weights_enabled", - True, - netuid, + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=True, + netuid=netuid, ), "Unable to enable commit reveal on the subnet" assert subtensor.subnets.commit_reveal_enabled(netuid), ( @@ -67,8 +67,8 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa # Lower the rate limit status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, ) @@ -84,8 +84,8 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa # Increase subnet tempo so we have enough time to commit and reveal weights sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={ "netuid": netuid, @@ -322,7 +322,7 @@ def get_weights_and_salt(counter: int): @pytest.mark.asyncio -async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wallet): +async def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): """ Tests that committing weights doesn't re-use nonce in the transaction pool. @@ -354,8 +354,8 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # weights sensitive to epoch changes assert sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_tempo", call_params={ "netuid": netuid, @@ -365,11 +365,11 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - local_chain, - alice_wallet, - "sudo_set_commit_reveal_weights_enabled", - True, - netuid, + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=True, + netuid=netuid, ), "Unable to enable commit reveal on the subnet" assert subtensor.commitments.commit_reveal_enabled(netuid), ( @@ -387,8 +387,8 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # Lower the rate limit status, error = sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, ) diff --git a/tests/e2e_tests/test_cross_subtensor_compatibility.py b/tests/e2e_tests/test_cross_subtensor_compatibility.py index 3198797643..44164b589e 100644 --- a/tests/e2e_tests/test_cross_subtensor_compatibility.py +++ b/tests/e2e_tests/test_cross_subtensor_compatibility.py @@ -4,7 +4,7 @@ @pytest.mark.asyncio -async def test_get_timestamp(subtensor, async_subtensor, local_chain): +async def test_get_timestamp(subtensor, async_subtensor): with subtensor: block_number = subtensor.chain.get_current_block() assert isinstance( From 8e66f56d5fcf380805123f3ea8e559baad07a519 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 11:44:26 -0700 Subject: [PATCH 83/86] while we support python 3.9 then `asyncio.gather` will break group repeating requests and return `RuntimeError` --- bittensor/core/async_subtensor.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1294c4a8f9..ba7693af2b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -811,15 +811,12 @@ async def all_subnets( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - query, subnet_prices = await asyncio.gather( - self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_all_dynamic_info", - block_hash=block_hash, - ), - self.get_subnet_prices(block_hash=block_hash), - return_exceptions=True, + query = await self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", + block_hash=block_hash, ) + subnet_prices = await self.get_subnet_prices(block_hash=block_hash) decoded = query.decode() From 3585428fe0daeec4ef95af063d4a278181966ce3 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 13:40:07 -0700 Subject: [PATCH 84/86] remove py3.9 support --- .github/workflows/_run-e2e-single.yaml | 2 +- .github/workflows/compatibility.yml | 2 +- .github/workflows/flake8-and-mypy.yml | 5 ++--- .github/workflows/monitor_requirements_size_master.yml | 4 +--- .github/workflows/nightly-e2e-tests-subtensor-main.yml | 8 ++++---- .github/workflows/release.yml | 2 +- .github/workflows/unit-and-integration-tests.yml | 5 ++--- migration.md | 3 ++- pyproject.toml | 4 ++-- scripts/check_pre_submit.sh | 2 +- 10 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 3464d8d1a3..16c41269af 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Check-out repository diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml index 2ef9a1b95f..88701bf246 100644 --- a/.github/workflows/compatibility.yml +++ b/.github/workflows/compatibility.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/flake8-and-mypy.yml b/.github/workflows/flake8-and-mypy.yml index 2e259bdd05..e0be7ce557 100644 --- a/.github/workflows/flake8-and-mypy.yml +++ b/.github/workflows/flake8-and-mypy.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false max-parallel: 5 matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: [3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout repository @@ -42,8 +42,7 @@ jobs: python -m venv venv source venv/bin/activate python -m pip install --upgrade pip - # needed for Python 3.9 compatibility - python -m pip install uv>=0.8.8 + python -m pip install uv==0.8.14 python -m uv sync --extra dev --active - name: Flake8 diff --git a/.github/workflows/monitor_requirements_size_master.yml b/.github/workflows/monitor_requirements_size_master.yml index e9efa8dcd3..664ab14f6a 100644 --- a/.github/workflows/monitor_requirements_size_master.yml +++ b/.github/workflows/monitor_requirements_size_master.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: [3.10", "3.11", "3.12", "3.13"] outputs: py39: ${{ steps.set-output.outputs.py39 }} py310: ${{ steps.set-output.outputs.py310 }} @@ -47,7 +47,6 @@ jobs: VERSION=${{ matrix.python-version }} echo "Detected size: $SIZE MB for Python $VERSION" case "$VERSION" in - 3.9) echo "py39=$SIZE" >> $GITHUB_OUTPUT ;; 3.10) echo "py310=$SIZE" >> $GITHUB_OUTPUT ;; 3.11) echo "py311=$SIZE" >> $GITHUB_OUTPUT ;; 3.12) echo "py312=$SIZE" >> $GITHUB_OUTPUT ;; @@ -64,7 +63,6 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const sizes = { - "3.9": "${{ needs.measure-venv.outputs.py39 || 'N/A' }}", "3.10": "${{ needs.measure-venv.outputs.py310 || 'N/A' }}", "3.11": "${{ needs.measure-venv.outputs.py311 || 'N/A' }}", "3.12": "${{ needs.measure-venv.outputs.py312 || 'N/A' }}", diff --git a/.github/workflows/nightly-e2e-tests-subtensor-main.yml b/.github/workflows/nightly-e2e-tests-subtensor-main.yml index 856bf6347b..0ab9f006ca 100644 --- a/.github/workflows/nightly-e2e-tests-subtensor-main.yml +++ b/.github/workflows/nightly-e2e-tests-subtensor-main.yml @@ -118,7 +118,7 @@ jobs: os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Check-out repository uses: actions/checkout@v4 @@ -191,7 +191,7 @@ jobs: os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Check-out repository uses: actions/checkout@v4 @@ -266,7 +266,7 @@ jobs: os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Check-out repository @@ -342,7 +342,7 @@ jobs: os: - ubuntu-latest test-file: ${{ fromJson(needs.find-tests.outputs.test-files) }} - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Check-out repository diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1118590448..f5a988b42a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.11' - name: Install dependencies run: | diff --git a/.github/workflows/unit-and-integration-tests.yml b/.github/workflows/unit-and-integration-tests.yml index 89089c3f60..c5578108aa 100644 --- a/.github/workflows/unit-and-integration-tests.yml +++ b/.github/workflows/unit-and-integration-tests.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false max-parallel: 5 matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout repository @@ -39,8 +39,7 @@ jobs: python -m venv venv source venv/bin/activate python -m pip install --upgrade pip - # needed for Python 3.9 compatibility - python -m pip install uv>=0.8.8 + python -m pip install uv>=0.8.14 python -m uv sync --extra dev --active - name: Unit tests diff --git a/migration.md b/migration.md index 62a698be05..d0b2f3e01c 100644 --- a/migration.md +++ b/migration.md @@ -105,6 +105,7 @@ rename this variable in documentation. 12. Find and process all `TODOs` across the entire code base. If in doubt, discuss each one with the team separately. SDK has 29 TODOs. + ## New features 1. Add `bittensor.utils.hex_to_ss58` function. SDK still doesn't have it. (Probably inner import `from scalecodec import ss58_encode, ss58_decode`) 2. Implement Crowdloan logic. @@ -143,4 +144,4 @@ It must include: # Migration guide -_Step-by-step explanation of breaking changes ..._ \ No newline at end of file +-[x] The SDK is dropping support for `Python 3.9` starting with this release. \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 40a7e7f85f..0cacc583b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ {name = "bittensor.com"} ] license = { file = "LICENSE" } -requires-python = ">=3.9,<3.14" +requires-python = ">=3.10,<3.14" dependencies = [ "wheel", @@ -81,10 +81,10 @@ classifiers = [ "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Mathematics", "Topic :: Scientific/Engineering :: Artificial Intelligence", diff --git a/scripts/check_pre_submit.sh b/scripts/check_pre_submit.sh index da2ef67eea..8ed3598ebc 100755 --- a/scripts/check_pre_submit.sh +++ b/scripts/check_pre_submit.sh @@ -7,7 +7,7 @@ ruff format . echo ">>> Run the pre-submit format check with \`mypy\`." # mypy checks python versions compatibility -versions=("3.9" "3.10" "3.11") +versions=("3.10" "3.11", "3.12", "3.13") for version in "${versions[@]}"; do echo "Running mypy for Python $version..." mypy --ignore-missing-imports bittensor/ --python-version="$version" From ea37fb51de9b2a597a435fdc3603ee6d9d752c3c Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 13:40:29 -0700 Subject: [PATCH 85/86] bring back gather logic --- bittensor/core/async_subtensor.py | 53 +++++++++++++++++-------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ba7693af2b..0821998264 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -45,6 +45,12 @@ set_children_extrinsic, ) from bittensor.core.extrinsics.asyncex.commit_reveal import commit_reveal_v3_extrinsic +from bittensor.core.extrinsics.asyncex.liquidity import ( + add_liquidity_extrinsic, + modify_liquidity_extrinsic, + remove_liquidity_extrinsic, + toggle_user_liquidity_extrinsic, +) from bittensor.core.extrinsics.asyncex.move_stake import ( transfer_stake_extrinsic, swap_stake_extrinsic, @@ -99,18 +105,12 @@ u64_normalized_float, get_transfer_fn_params, ) -from bittensor.core.extrinsics.asyncex.liquidity import ( - add_liquidity_extrinsic, - modify_liquidity_extrinsic, - remove_liquidity_extrinsic, - toggle_user_liquidity_extrinsic, -) +from bittensor.utils import deprecated_message from bittensor.utils.balance import ( Balance, fixed_to_float, check_and_convert_to_balance, ) -from bittensor.utils import deprecated_message from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import ( calculate_fees, @@ -811,13 +811,15 @@ async def all_subnets( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - query = await self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_all_dynamic_info", - block_hash=block_hash, + query, subnet_prices = await asyncio.gather( + self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_all_dynamic_info", + block_hash=block_hash, + ), + self.get_subnet_prices(block_hash=block_hash), + return_exceptions=True, ) - subnet_prices = await self.get_subnet_prices(block_hash=block_hash) - decoded = query.decode() if not isinstance(subnet_prices, (SubstrateRequestException, ValueError)): @@ -3904,17 +3906,20 @@ async def subnet( if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - query = await self.substrate.runtime_call( - "SubnetInfoRuntimeApi", - "get_dynamic_info", - params=[netuid], - block_hash=block_hash, - ) - price = await self.get_subnet_price( - netuid=netuid, - block=block, - block_hash=block_hash, - reuse_block=reuse_block, + query, price = await asyncio.gather( + self.substrate.runtime_call( + "SubnetInfoRuntimeApi", + "get_dynamic_info", + params=[netuid], + block_hash=block_hash, + ), + self.get_subnet_price( + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ), + return_exceptions=True, ) if isinstance(decoded := query.decode(), dict): From 88281e6209c9d09f4f391dadb7cf3a04597897f1 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 13:41:45 -0700 Subject: [PATCH 86/86] quotes --- .github/workflows/flake8-and-mypy.yml | 2 +- .github/workflows/monitor_requirements_size_master.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flake8-and-mypy.yml b/.github/workflows/flake8-and-mypy.yml index e0be7ce557..db0b0087d8 100644 --- a/.github/workflows/flake8-and-mypy.yml +++ b/.github/workflows/flake8-and-mypy.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false max-parallel: 5 matrix: - python-version: [3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - name: Checkout repository diff --git a/.github/workflows/monitor_requirements_size_master.yml b/.github/workflows/monitor_requirements_size_master.yml index 664ab14f6a..459e02c776 100644 --- a/.github/workflows/monitor_requirements_size_master.yml +++ b/.github/workflows/monitor_requirements_size_master.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] outputs: py39: ${{ steps.set-output.outputs.py39 }} py310: ${{ steps.set-output.outputs.py310 }}