From be9f25317c7828675d2a05e67bce6957d00f511a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 14:52:00 -0700 Subject: [PATCH 001/177] add `migration.md` --- migration.md | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 migration.md diff --git a/migration.md b/migration.md new file mode 100644 index 0000000000..fdcb521631 --- /dev/null +++ b/migration.md @@ -0,0 +1,145 @@ +# Plan + +## Extrinsics and related +1. Standardize parameter order across all extrinsics and related calls. Pass extrinsic-specific arguments first (e.g., wallet, hotkey, netuid, amount), followed by optional general flags (e.g., wait_for_inclusion, wait_for_finalization) +
+ Example + + ```py + def swap_stake_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + hotkey_ss58: str, + origin_netuid: int, + destination_netuid: int, + amount: Optional[Balance] = None, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + safe_staking: bool = False, + allow_partial_stake: bool = False, + rate_tolerance: float = 0.005, + period: Optional[int] = None, + ) -> bool: + ``` + it will be + ```py + def swap_stake_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + hotkey_ss58: str, + origin_netuid: int, + destination_netuid: int, + amount: Optional[Balance] = None, + rate_tolerance: float = 0.005, + allow_partial_stake: bool = False, + safe_staking: bool = False, + period: Optional[int] = None, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, + ) -> bool: + ``` +
+ +2. Unify extrinsic return values by introducing an ExtrinsicResponse class. Extrinsics currently return either a boolean or a tuple. + + Purpose: + - Ease of processing + - This class should contain success, message, and optionally data and logs. (to save all logs during the extrinsic) + +3. Set `wait_for_inclusion` and `wait_for_finalization` to `True` by default in extrinsics and their related calls. Then we will guarantee the correct/expected extrinsic call response is consistent with the chain response. If the user changes those values, then it is the user's responsibility. +4. Make the internal logic of extrinsics the same. There are extrinsics that are slightly different in implementation. + +5. Since SDK is not a responsible tool, try to remove all calculations inside extrinsics that do not affect the result, but are only used in logging. Actually, this should be applied not to extrinsics only but for all codebase. + +6. Remove `unstake_all` parameter from `unstake_extrinsic` since we have `unstake_all_extrinsic`which is calles another subtensor function. + +7. `unstake` and `unstake_multiple` extrinsics should have `safe_unstaking` parameters instead of `safe_staking`. + +8. Remove `_do*` extrinsic calls and combine them with extrinsic logic. + +9. `subtensor.get_transfer_fee` calls extrinsic inside the subtensor module. Actually the method could be updated by using `bittensor.core.extrinsics.utils.get_extrinsic_fee`. + +## Subtensor +1. In the synchronous Subtensor class, the `get_owned_hotkeys` method includes a `reuse_block` parameter that is inconsistent with other methods. Either remove this parameter from `get_owned_hotkeys`, or add it to all other methods that directly call self.substrate.* to maintain a consistent interface. +2. In all methods where we `get_stake_operations_fee` is called, remove unused arguments. Consider combining all methods using `get_stake_operations_fee` into one common one. +3. Delete deprecated `get_current_weight_commit_info` and `get_current_weight_commit_info_v2`. Rename `get_timelocked_weight_commits` to get_current_weight_commit_info. +4. Remove references like `get_stake_info_for_coldkey = get_stake_for_coldkey`. +5. Reconsider some methods naming across the entire subtensor module. +6. Add `hotkey_ss58` parameter to `get_liquidity_list` method. One wallet can have many HKs. Currently, the mentioned method uses default HK only. + +## Metagraph +1. Remove verbose archival node warnings for blocks older than 300. Some users complained about many messages for them. +2. Reconsider entire metagraph module logic. + +## Balance +1. In `bittensor.utils.balance._check_currencies` raise the error instead of `warnings.warn`. +2. In `bittensor.utils.balance.check_and_convert_to_balance` raise the error instead of `warnings.warn`. +This may seem like a harsh decision at first, but ultimately we will push the community to use Balance and there will be fewer errors in their calculations. Confusion with TAO and Alpha in calculations and display/printing/logging will be eliminated. + +## Common things +1. Reduce the amount of logging.info or transfer part of logging.info to logging.debug + +2. To be consistent across all SDK regarding local environment variables name: +remove `BT_CHAIN_ENDPOINT` (settings.py :line 124) and use `BT_SUBTENSOR_CHAIN_ENDPOINT` instead of that. +rename this variable in documentation. + +3. Move `bittensor.utils.get_transfer_fn_params` to `bittensor.core.extrinsics.utils`. + +4. Common refactoring (improve type annotations, etc) + +5. Rename `non-/fast-blocks` to `non-/fast-runtime` in related places to be consistent with subtensor repo. Related with testing, subtensor scripts, documentation. + +6. To be consistent throughout the SDK: +`hotkey`, `coldkey`, `hotkeypub`, and `coldkeypub` are keypairs +`hotkey_ss58`, `coldkey_ss58`, `hotkeypub_ss58`, and `coldkeypub_ss58` are SS58 addresses of keypair. + +7. Replace `Arguments` with `Parameters`. Matches Python rules. Improve docstrings for writing MСP server. + +8. Remove all type annotations for parameters in docstrings. + +9. Remove all logic related to CRv3 as it will be removed from the chain next week. + +10. Revise `bittensor/utils/easy_imports.py` module to remove deprecated backwards compatibility objects. Use this module as a functionality for exporting existing objects to the package root to keep __init__.py minimal and simple. + +11. Remove `bittensor.utils.version.version_checking` + +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. +3. “Implement Sub-subnets / Metagraph Changes?” (implementation unsure) Maciej Kula idea, requires mode details. + +## Testing +1. When running tests via Docker, ensure no lingering processes occupy required ports before launch. + +2. Improve failed test reporting from GH Actions to the Docker channel (e.g., clearer messages, formatting). + +3. Write a configurable test harness class for tests that will accept arguments and immediately: +create a subnet +activate a subnet (if the argument is passed as True) +register neurons (use wallets as arguments) +set the necessary hyperparameters (tempo, etc. if the argument are passed) +Will greatly simplify tests. + +4. Add an async test versions. This will help us greatly improve the asynchronous implementation of Subtensors and Extrinsics. + + +## Implementation + +To implement the above changes and prepare for the v10 release, the following steps must be taken: + +-[x] Create a new branch named SDKv10.~~ +All breaking changes and refactors should be targeted into this branch to isolate them from staging and maintain backward compatibility during development. +-[x] Add a `migration.md` document at the root of the repository and use it as a check list. This file will serve as a changelog and technical reference. +It must include: + - All change categories (Extrinsics, Subtensor, Metagraph, etc.) + - Per-PR breakdown of what was added, removed, renamed, or refactored. + - Justifications and migration notes for users (if API behavior changed). +-[ ] Based on the final `migration.md`, develop migration documentation for the community. +-[ ] Once complete, merge SDKv10 into staging and release version 10. + + +# Migration guide + +_Step-by-step explanation of breaking changes ..._ \ No newline at end of file From dd285afdea77a677e413697eff5320f3ee879960 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 14:56:03 -0700 Subject: [PATCH 002/177] add `migration.md` --- migration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/migration.md b/migration.md index fdcb521631..62a698be05 100644 --- a/migration.md +++ b/migration.md @@ -136,6 +136,7 @@ It must include: - All change categories (Extrinsics, Subtensor, Metagraph, etc.) - Per-PR breakdown of what was added, removed, renamed, or refactored. - Justifications and migration notes for users (if API behavior changed). + -[ ] Based on the final `migration.md`, develop migration documentation for the community. -[ ] Once complete, merge SDKv10 into staging and release version 10. From 49547ed0300c0b6862feb576a48da9dcb80e5743 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 15:06:29 -0700 Subject: [PATCH 003/177] 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 004/177] 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 005/177] 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 006/177] 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 007/177] 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 008/177] 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 009/177] 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 010/177] 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 011/177] 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 012/177] 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 c6d0db28e1d15ec2a48028cf7bb92e183e141eee Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 14:48:03 -0700 Subject: [PATCH 013/177] 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 014/177] 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 4031a22fb47f5289337b0e7355ab440823d7c660 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:01:57 -0700 Subject: [PATCH 015/177] 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 016/177] `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 017/177] 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 018/177] 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 019/177] 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 020/177] 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 021/177] 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 022/177] 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 023/177] 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 024/177] 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 025/177] 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 026/177] 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 027/177] 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 028/177] 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 029/177] 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 030/177] 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 031/177] 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 032/177] 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 033/177] 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 034/177] 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 035/177] 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 036/177] 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 037/177] 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 038/177] 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 039/177] 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 040/177] 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 041/177] 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 042/177] 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 043/177] 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 044/177] 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 045/177] 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 046/177] 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 047/177] 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 048/177] 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 049/177] 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 050/177] 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 051/177] 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 052/177] 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 053/177] 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 054/177] 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 055/177] 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 056/177] 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 057/177] 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 058/177] 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 059/177] 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 060/177] 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 061/177] 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 062/177] 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 063/177] 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 064/177] 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 065/177] 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 066/177] 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 067/177] 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 068/177] 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 069/177] 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 070/177] 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 071/177] 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 072/177] 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 073/177] `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 074/177] 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 075/177] 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 076/177] 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 077/177] `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 078/177] 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 079/177] 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 080/177] 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 081/177] 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 082/177] 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 }} From 77d6907b9a46af2ee9f1dd3d9cacaa1024db5088 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 14:48:29 -0700 Subject: [PATCH 083/177] `._do_commit_reveal_v3` is included in the main code `.commit_reveal_v3_extrinsic`, `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` --- bittensor/core/async_subtensor.py | 4 +- .../core/extrinsics/asyncex/commit_reveal.py | 85 +++++-------------- bittensor/core/extrinsics/commit_reveal.py | 85 +++++-------------- bittensor/core/subtensor.py | 4 +- 4 files changed, 46 insertions(+), 132 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 0821998264..118f60706f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -44,7 +44,7 @@ root_set_pending_childkey_cooldown_extrinsic, set_children_extrinsic, ) -from bittensor.core.extrinsics.asyncex.commit_reveal import commit_reveal_v3_extrinsic +from bittensor.core.extrinsics.asyncex.commit_reveal import commit_reveal_extrinsic from bittensor.core.extrinsics.asyncex.liquidity import ( add_liquidity_extrinsic, modify_liquidity_extrinsic, @@ -5283,7 +5283,7 @@ async def _blocks_weight_limit() -> bool: logging.info( f"Committing weights for subnet #{netuid}. Attempt {retries + 1} of {max_retries}." ) - success, message = await commit_reveal_v3_extrinsic( + success, message = await commit_reveal_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py index 652ceafa50..78a7ef4824 100644 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ b/bittensor/core/extrinsics/asyncex/commit_reveal.py @@ -16,65 +16,7 @@ from bittensor.utils.registration import torch -# TODO: Merge this logic with `commit_reveal_extrinsic` in SDKv10 bc this is not CRv3 anymore. -async def _do_commit_reveal_v3( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - commit: bytes, - reveal_round: int, - commit_reveal_version: int = 4, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = None, -) -> tuple[bool, str]: - """ - Executes commit-reveal phase 3 for a given netuid and commit, and optionally waits for extrinsic inclusion or finalization. - - Arguments: - subtensor: An instance of the AsyncSubtensor class. - wallet: Wallet An instance of the Wallet class containing the user's keypair. - netuid: int The network unique identifier. - commit: bytes The commit data in bytes format. - reveal_round: int The round number for the reveal phase. - commit_reveal_version: The version of the chain commit-reveal protocol to use. Default is ``4``. - wait_for_inclusion: bool, optional Flag indicating whether to wait for the extrinsic to be included in a block. - wait_for_finalization: bool, optional Flag indicating whether to wait for the extrinsic to be finalized. - 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. - - Returns: - A tuple where the first element is a boolean indicating success or failure, and the second element is a - string containing an error message if any. - """ - logging.info( - f"Committing weights hash [blue]{commit.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with " - f"reveal round [blue]{reveal_round}[/blue]..." - ) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_timelocked_weights", - call_params={ - "netuid": netuid, - "commit": commit, - "reveal_round": reveal_round, - "commit_reveal_version": commit_reveal_version, - }, - ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - ) - - -# TODO: rename this extrinsic to `commit_reveal_extrinsic` in SDK.v10 -async def commit_reveal_v3_extrinsic( +async def commit_reveal_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, @@ -85,6 +27,7 @@ async def commit_reveal_v3_extrinsic( wait_for_finalization: bool = False, block_time: Union[int, float] = 12.0, period: Optional[int] = None, + commit_reveal_version: int = 4, ) -> tuple[bool, str]: """ Commits and reveals weights for a given subtensor and wallet with provided uids and weights. @@ -102,6 +45,7 @@ async def commit_reveal_v3_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. + commit_reveal_version: The version of the chain commit-reveal protocol to use. Default is ``4``. Returns: tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure, and the second @@ -130,14 +74,27 @@ async def commit_reveal_v3_extrinsic( hotkey=wallet.hotkey.public_key, ) - success, message = await _do_commit_reveal_v3( - subtensor=subtensor, + logging.info( + f"Committing weights hash [blue]{commit_for_reveal.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with " + f"reveal round [blue]{reveal_round}[/blue]..." + ) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_timelocked_weights", + call_params={ + "netuid": netuid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + }, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - netuid=netuid, - commit=commit_for_reveal, - reveal_round=reveal_round, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + sign_with="hotkey", period=period, ) diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index 7709619d7e..2f83229b33 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -16,65 +16,7 @@ from bittensor.utils.registration import torch -# TODO: Merge this logic with `commit_reveal_extrinsic` in SDKv10 bc this is not CRv3 anymore. -def _do_commit_reveal_v3( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - commit: bytes, - reveal_round: int, - commit_reveal_version: int = 4, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = None, -) -> tuple[bool, str]: - """ - Executes commit-reveal extrinsic for a given netuid, commit, reveal_round, and commit_reveal_version. - - Arguments: - subtensor: An instance of the Subtensor class. - wallet: Wallet An instance of the Wallet class containing the user's keypair. - netuid: int The network unique identifier. - commit: bytes The commit data in bytes format. - reveal_round: int The round number for the reveal phase. - commit_reveal_version: The version of the chain commit-reveal protocol to use. Default is ``4``. - wait_for_inclusion: bool, optional Flag indicating whether to wait for the extrinsic to be included in a block. - wait_for_finalization: bool, optional Flag indicating whether to wait for the extrinsic to be finalized. - 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. - - Returns: - A tuple where the first element is a boolean indicating success or failure, and the second element is a string - containing an error message if any. - """ - logging.info( - f"Committing weights hash [blue]{commit.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with " - f"reveal round [blue]{reveal_round}[/blue]..." - ) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_timelocked_weights", - call_params={ - "netuid": netuid, - "commit": commit, - "reveal_round": reveal_round, - "commit_reveal_version": commit_reveal_version, - }, - ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - ) - - -# TODO: rename this extrinsic to `commit_reveal_extrinsic` in SDK.v10 -def commit_reveal_v3_extrinsic( +def commit_reveal_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, @@ -85,6 +27,7 @@ def commit_reveal_v3_extrinsic( wait_for_finalization: bool = False, block_time: Union[int, float] = 12.0, period: Optional[int] = None, + commit_reveal_version: int = 4, ) -> tuple[bool, str]: """ Commits and reveals weights for a given subtensor and wallet with provided uids and weights. @@ -102,6 +45,7 @@ def commit_reveal_v3_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. + commit_reveal_version: The version of the chain commit-reveal protocol to use. Default is ``4``. Returns: tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure, and the second @@ -130,14 +74,27 @@ def commit_reveal_v3_extrinsic( hotkey=wallet.hotkey.public_key, ) - success, message = _do_commit_reveal_v3( - subtensor=subtensor, + logging.info( + f"Committing weights hash [blue]{commit_for_reveal.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with " + f"reveal round [blue]{reveal_round}[/blue]..." + ) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_timelocked_weights", + call_params={ + "netuid": netuid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - netuid=netuid, - commit=commit_for_reveal, - reveal_round=reveal_round, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + sign_with="hotkey", period=period, ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e27963947a..f9a82faa36 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -43,7 +43,7 @@ set_children_extrinsic, root_set_pending_childkey_cooldown_extrinsic, ) -from bittensor.core.extrinsics.commit_reveal import commit_reveal_v3_extrinsic +from bittensor.core.extrinsics.commit_reveal import commit_reveal_extrinsic from bittensor.core.extrinsics.commit_weights import ( commit_weights_extrinsic, reveal_weights_extrinsic, @@ -4088,7 +4088,7 @@ def _blocks_weight_limit() -> bool: f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - success, message = commit_reveal_v3_extrinsic( + success, message = commit_reveal_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, From 89484f2e8307802b90c1d082d9d1d9b728039eb7 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 14:49:24 -0700 Subject: [PATCH 084/177] update tests --- tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py | 8 ++++---- tests/unit_tests/extrinsics/test_commit_reveal.py | 8 ++++---- tests/unit_tests/test_subtensor.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index 137a5d8d41..cbeb890e65 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -197,7 +197,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( ) # Call - success, message = await async_commit_reveal.commit_reveal_v3_extrinsic( + success, message = await async_commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -265,7 +265,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_numpy( ) # Call - success, message = await async_commit_reveal.commit_reveal_v3_extrinsic( + success, message = await async_commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -317,7 +317,7 @@ async def test_commit_reveal_v3_extrinsic_response_false( ) # Call - success, message = await async_commit_reveal.commit_reveal_v3_extrinsic( + success, message = await async_commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -357,7 +357,7 @@ async def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor, fake_wall ) # Call - success, message = await async_commit_reveal.commit_reveal_v3_extrinsic( + success, message = await async_commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index 42ae3e14d9..35eb6c2163 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -181,7 +181,7 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( ) # Call - success, message = commit_reveal.commit_reveal_v3_extrinsic( + success, message = commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -249,7 +249,7 @@ def test_commit_reveal_v3_extrinsic_success_with_numpy( ) # Call - success, message = commit_reveal.commit_reveal_v3_extrinsic( + success, message = commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -300,7 +300,7 @@ def test_commit_reveal_v3_extrinsic_response_false( ) # Call - success, message = commit_reveal.commit_reveal_v3_extrinsic( + success, message = commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, @@ -339,7 +339,7 @@ def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor, fake_wallet): ) # Call - success, message = commit_reveal.commit_reveal_v3_extrinsic( + success, message = commit_reveal.commit_reveal_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=fake_netuid, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 5d456456c4..2980cec909 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3153,7 +3153,7 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): subtensor, "commit_reveal_enabled", return_value=True ) mocked_commit_reveal_v3_extrinsic = mocker.patch.object( - subtensor_module, "commit_reveal_v3_extrinsic" + subtensor_module, "commit_reveal_extrinsic" ) mocked_commit_reveal_v3_extrinsic.return_value = ( True, From f6cb21096259f082568236adf122f5a4313528bd Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 15:00:35 -0700 Subject: [PATCH 085/177] removed deprecated tests, fix another --- .../extrinsics/asyncex/test_commit_reveal.py | 151 +++--------------- .../extrinsics/test_commit_reveal.py | 138 +++------------- 2 files changed, 46 insertions(+), 243 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index cbeb890e65..7b2a74aeb9 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -45,114 +45,6 @@ def hyperparams(): ) -@pytest.mark.asyncio -async def test_do_commit_reveal_v3_success(mocker, subtensor, fake_wallet): - """Test successful commit-reveal with wait for finalization.""" - # Preps - fake_netuid = 1 - fake_commit = b"fake_commit" - fake_reveal_round = 1 - - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_create_signed_extrinsic = mocker.patch.object( - subtensor.substrate, "create_signed_extrinsic" - ) - mocked_submit_extrinsic = mocker.patch.object( - subtensor.substrate, "submit_extrinsic" - ) - - # Call - result = await async_commit_reveal._do_commit_reveal_v3( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit, - reveal_round=fake_reveal_round, - ) - - # Asserts - mocked_compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="commit_timelocked_weights", - call_params={ - "netuid": fake_netuid, - "commit": fake_commit, - "reveal_round": fake_reveal_round, - "commit_reveal_version": 4, - }, - ) - mocked_create_signed_extrinsic.assert_awaited_once_with( - call=mocked_compose_call.return_value, keypair=fake_wallet.hotkey - ) - mocked_submit_extrinsic.assert_awaited_once_with( - mocked_create_signed_extrinsic.return_value, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - assert result == (True, "Not waiting for finalization or inclusion.") - - -@pytest.mark.asyncio -async def test_do_commit_reveal_v3_failure_due_to_error(mocker, subtensor, fake_wallet): - """Test commit-reveal fails due to an error in submission.""" - # Preps - fake_netuid = 1 - fake_commit = b"fake_commit" - fake_reveal_round = 1 - - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_create_signed_extrinsic = mocker.patch.object( - subtensor.substrate, "create_signed_extrinsic" - ) - mocked_submit_extrinsic = mocker.patch.object( - subtensor.substrate, - "submit_extrinsic", - return_value=mocker.Mock( - is_success=mocker.AsyncMock(return_value=False)(), - error_message=mocker.AsyncMock(return_value="Mocked error")(), - ), - ) - - mocked_format_error_message = mocker.patch.object( - subtensor_module, - "format_error_message", - return_value="Formatted error", - ) - - # Call - result = await async_commit_reveal._do_commit_reveal_v3( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit, - reveal_round=fake_reveal_round, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - mocked_compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="commit_timelocked_weights", - call_params={ - "netuid": fake_netuid, - "commit": fake_commit, - "reveal_round": fake_reveal_round, - "commit_reveal_version": 4, - }, - ) - mocked_create_signed_extrinsic.assert_awaited_once_with( - call=mocked_compose_call.return_value, keypair=fake_wallet.hotkey - ) - mocked_submit_extrinsic.assert_awaited_once_with( - mocked_create_signed_extrinsic.return_value, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - mocked_format_error_message.assert_called_once_with("Mocked error") - assert result == (False, "Formatted error") - - @pytest.mark.asyncio async def test_commit_reveal_v3_extrinsic_success_with_torch( mocker, subtensor, hyperparams, fake_wallet @@ -174,16 +66,14 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( "convert_and_normalize_weights_and_uids", return_value=(mocked_uids, mocked_weights), ) - mocked_get_subnet_reveal_period_epochs = mocker.patch.object( - subtensor, "get_subnet_reveal_period_epochs" - ) mocked_get_encrypted_commit = mocker.patch.object( async_commit_reveal, "get_encrypted_commit", return_value=(fake_commit_for_reveal, fake_reveal_round), ) - mock_do_commit_reveal_v3 = mocker.patch.object( - async_commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Success") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) mock_block = mocker.patch.object( subtensor.substrate, @@ -224,14 +114,12 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( block_time=12.0, hotkey=fake_wallet.hotkey.public_key, ) - mock_do_commit_reveal_v3.assert_awaited_once_with( - subtensor=subtensor, + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit_for_reveal, - reveal_round=fake_reveal_round, wait_for_inclusion=True, wait_for_finalization=True, + sign_with="hotkey", period=None, ) @@ -254,8 +142,9 @@ async def test_commit_reveal_v3_extrinsic_success_with_numpy( mock_encode_drand = mocker.patch.object( async_commit_reveal, "get_encrypted_commit", return_value=(b"commit", 0) ) - mock_do_commit = mocker.patch.object( - async_commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Committed!") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1) mocker.patch.object( @@ -280,7 +169,14 @@ async def test_commit_reveal_v3_extrinsic_success_with_numpy( assert message == "reveal_round:0" mock_convert.assert_called_once_with(fake_uids, fake_weights) mock_encode_drand.assert_called_once() - mock_do_commit.assert_awaited_once() + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=False, + wait_for_finalization=False, + sign_with="hotkey", + period=None, + ) @pytest.mark.asyncio @@ -306,8 +202,9 @@ async def test_commit_reveal_v3_extrinsic_response_false( "get_encrypted_commit", return_value=(fake_commit_for_reveal, fake_reveal_round), ) - mock_do_commit_reveal_v3 = mocker.patch.object( - async_commit_reveal, "_do_commit_reveal_v3", return_value=(False, "Failed") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(False, "Failed") ) mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1) mocker.patch.object( @@ -330,14 +227,12 @@ async def test_commit_reveal_v3_extrinsic_response_false( # Asserts assert success is False assert message == "Failed" - mock_do_commit_reveal_v3.assert_awaited_once_with( - subtensor=subtensor, + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit_for_reveal, - reveal_round=fake_reveal_round, wait_for_inclusion=True, wait_for_finalization=True, + sign_with="hotkey", period=None, ) diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index 35eb6c2163..b0c2da977c 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -46,104 +46,6 @@ def hyperparams(): ) -def test_do_commit_reveal_v3_success(mocker, subtensor, fake_wallet): - """Test successful commit-reveal with wait for finalization.""" - # Preps - fake_netuid = 1 - fake_commit = b"fake_commit" - fake_reveal_round = 1 - - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "") - ) - - # Call - result = commit_reveal._do_commit_reveal_v3( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit, - reveal_round=fake_reveal_round, - ) - - # Asserts - mocked_compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="commit_timelocked_weights", - call_params={ - "netuid": fake_netuid, - "commit": fake_commit, - "reveal_round": fake_reveal_round, - "commit_reveal_version": 4, - }, - ) - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=False, - wait_for_finalization=False, - sign_with="hotkey", - period=None, - ) - - assert result == (True, "") - - -def test_do_commit_reveal_v3_failure_due_to_error(mocker, subtensor, fake_wallet): - """Test commit-reveal fails due to an error in submission.""" - # Preps - fake_netuid = 1 - fake_commit = b"fake_commit" - fake_reveal_round = 1 - - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_create_signed_extrinsic = mocker.patch.object( - subtensor.substrate, "create_signed_extrinsic" - ) - mocked_submit_extrinsic = mocker.patch.object( - subtensor.substrate, - "submit_extrinsic", - return_value=mocker.Mock(is_success=False, error_message="Mocked error"), - ) - mocked_format_error_message = mocker.patch.object( - subtensor_module, "format_error_message", return_value="Formatted error" - ) - - # Call - result = commit_reveal._do_commit_reveal_v3( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit, - reveal_round=fake_reveal_round, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - mocked_compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="commit_timelocked_weights", - call_params={ - "netuid": fake_netuid, - "commit": fake_commit, - "reveal_round": fake_reveal_round, - "commit_reveal_version": 4, - }, - ) - mocked_create_signed_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, keypair=fake_wallet.hotkey - ) - mocked_submit_extrinsic.assert_called_once_with( - mocked_create_signed_extrinsic.return_value, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - mocked_format_error_message.assert_called_once_with("Mocked error") - assert result == (False, "Formatted error") - - def test_commit_reveal_v3_extrinsic_success_with_torch( mocker, subtensor, hyperparams, fake_wallet ): @@ -170,8 +72,9 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( "get_encrypted_commit", return_value=(fake_commit_for_reveal, fake_reveal_round), ) - mock_do_commit_reveal_v3 = mocker.patch.object( - commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Success") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) mock_block = mocker.patch.object(subtensor, "get_current_block", return_value=1) mock_hyperparams = mocker.patch.object( @@ -209,14 +112,12 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( block_time=12.0, hotkey=fake_wallet.hotkey.public_key, ) - mock_do_commit_reveal_v3.assert_called_once_with( - subtensor=subtensor, + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit_for_reveal, - reveal_round=fake_reveal_round, wait_for_inclusion=True, wait_for_finalization=True, + sign_with="hotkey", period=None, ) @@ -238,8 +139,9 @@ def test_commit_reveal_v3_extrinsic_success_with_numpy( mock_encode_drand = mocker.patch.object( commit_reveal, "get_encrypted_commit", return_value=(b"commit", 0) ) - mock_do_commit = mocker.patch.object( - commit_reveal, "_do_commit_reveal_v3", return_value=(True, "Committed!") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) mocker.patch.object(subtensor, "get_current_block", return_value=1) mocker.patch.object( @@ -264,7 +166,14 @@ def test_commit_reveal_v3_extrinsic_success_with_numpy( assert message == "reveal_round:0" mock_convert.assert_called_once_with(fake_uids, fake_weights) mock_encode_drand.assert_called_once() - mock_do_commit.assert_called_once() + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=False, + wait_for_finalization=False, + sign_with="hotkey", + period=None, + ) def test_commit_reveal_v3_extrinsic_response_false( @@ -289,8 +198,9 @@ def test_commit_reveal_v3_extrinsic_response_false( "get_encrypted_commit", return_value=(fake_commit_for_reveal, fake_reveal_round), ) - mock_do_commit_reveal_v3 = mocker.patch.object( - commit_reveal, "_do_commit_reveal_v3", return_value=(False, "Failed") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(False, "Failed") ) mocker.patch.object(subtensor, "get_current_block", return_value=1) mocker.patch.object( @@ -313,14 +223,12 @@ def test_commit_reveal_v3_extrinsic_response_false( # Asserts assert success is False assert message == "Failed" - mock_do_commit_reveal_v3.assert_called_once_with( - subtensor=subtensor, + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuid=fake_netuid, - commit=fake_commit_for_reveal, - reveal_round=fake_reveal_round, wait_for_inclusion=True, wait_for_finalization=True, + sign_with="hotkey", period=None, ) From 3220e853cad945ef4c82a28b04c5e3100c83c86e Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 15:00:50 -0700 Subject: [PATCH 086/177] update migration.md --- migration.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/migration.md b/migration.md index d0b2f3e01c..30d832d9b3 100644 --- a/migration.md +++ b/migration.md @@ -19,6 +19,7 @@ allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = True, ) -> bool: ``` it will be @@ -36,6 +37,7 @@ period: Optional[int] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + raise_error: bool = True, ) -> bool: ``` @@ -55,7 +57,7 @@ 7. `unstake` and `unstake_multiple` extrinsics should have `safe_unstaking` parameters instead of `safe_staking`. -8. Remove `_do*` extrinsic calls and combine them with extrinsic logic. +8. ✅ Remove `_do*` extrinsic calls and combine them with extrinsic logic. 9. `subtensor.get_transfer_fee` calls extrinsic inside the subtensor module. Actually the method could be updated by using `bittensor.core.extrinsics.utils.get_extrinsic_fee`. @@ -104,7 +106,7 @@ rename this variable in documentation. 11. Remove `bittensor.utils.version.version_checking` 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. - +13. ✅ The SDK is dropping support for `Python 3.9` starting with this release.~~ ## 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`) @@ -132,11 +134,11 @@ To implement the above changes and prepare for the v10 release, the following st -[x] Create a new branch named SDKv10.~~ All breaking changes and refactors should be targeted into this branch to isolate them from staging and maintain backward compatibility during development. --[x] Add a `migration.md` document at the root of the repository and use it as a check list. This file will serve as a changelog and technical reference. +-[ ] Add a `migration.md` document at the root of the repository and use it as a check list. This file will serve as a changelog and technical reference. It must include: - - All change categories (Extrinsics, Subtensor, Metagraph, etc.) - - Per-PR breakdown of what was added, removed, renamed, or refactored. - - Justifications and migration notes for users (if API behavior changed). + -[ ] All change categories (Extrinsics, Subtensor, Metagraph, etc.) + -[ ] Per-PR breakdown of what was added, removed, renamed, or refactored. + -[ ] Justifications and migration notes for users (if API behavior changed). -[ ] Based on the final `migration.md`, develop migration documentation for the community. -[ ] Once complete, merge SDKv10 into staging and release version 10. @@ -144,4 +146,6 @@ It must include: # Migration guide --[x] The SDK is dropping support for `Python 3.9` starting with this release. \ No newline at end of file +-[x] `._do_commit_reveal_v3` logic is included in the main code `.commit_reveal_v3_extrinsic` +-[x] `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` +-[x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` \ No newline at end of file From 3e9361b15ee5428aa795394475df17eed55b6081 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 15:28:04 -0700 Subject: [PATCH 087/177] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` + fixed/removed tests --- bittensor/core/extrinsics/asyncex/weights.py | 66 +------ bittensor/core/extrinsics/commit_weights.py | 70 ++----- migration.md | 3 +- .../extrinsics/asyncex/test_weights.py | 184 +++--------------- .../extrinsics/test_commit_weights.py | 53 ----- 5 files changed, 50 insertions(+), 326 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 6d06210702..8a541f351b 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -15,7 +15,7 @@ from bittensor.utils.registration import torch -async def _do_commit_weights( +async def commit_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, @@ -26,11 +26,12 @@ async def _do_commit_weights( 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. - This method constructs and submits the transaction, handling retries and blockchain communication. + Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. + This function is a wrapper around the `do_commit_weights` method. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain interaction. + subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain + interaction. wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. netuid (int): The unique identifier of the subnet. commit_hash (str): The hash of the neuron's weights to be committed. @@ -46,8 +47,8 @@ async def _do_commit_weights( `True` if the weight commitment is successful, `False` otherwise. `msg` is a string value describing the success or potential error. - This method ensures that the weight commitment is securely recorded on the Bittensor blockchain, providing a - verifiable record of the neuron's weight distribution at a specific point in time. + This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper + error handling and user interaction when required. """ call = await subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -57,7 +58,7 @@ async def _do_commit_weights( "commit_hash": commit_hash, }, ) - return await subtensor.sign_and_send_extrinsic( + success, message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -69,60 +70,13 @@ async def _do_commit_weights( raise_error=raise_error, ) - -async def commit_weights_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - commit_hash: str, - 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. - This function is a wrapper around the `do_commit_weights` method. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain - interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. - netuid (int): The unique identifier of the subnet. - commit_hash (str): The hash of the neuron's weights to be committed. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. - - This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper - error handling and user interaction when required. - """ - success, error_message = await _do_commit_weights( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - commit_hash=commit_hash, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) - if success: success_message = "✅ [green]Successfully committed weights.[green]" logging.info(success_message) return True, success_message - logging.error(f"Failed to commit weights: {error_message}") - return False, error_message + logging.error(f"Failed to commit weights: {message}") + return False, message async def _do_reveal_weights( diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index 00b3e4cb6c..5a8ecb77bf 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -9,7 +9,7 @@ from bittensor.core.subtensor import Subtensor -def _do_commit_weights( +def commit_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, @@ -17,11 +17,11 @@ def _do_commit_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = None, - raise_error: bool = True, + 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. - This method constructs and submits the transaction, handling retries and blockchain communication. + Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. + This function is a wrapper around the `do_commit_weights` method. Args: subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance used for blockchain interaction. @@ -33,16 +33,17 @@ 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. + raise_error (bool): Whether to raise an error if the transaction fails. Returns: tuple[bool, str]: `True` if the weight commitment is successful, `False` otherwise. `msg` is a string value describing the success or potential error. - This method ensures that the weight commitment is securely recorded on the Bittensor blockchain, providing a - verifiable record of the neuron's weight distribution at a specific point in time. + This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper + error handling and user interaction when required. """ + call = subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="commit_weights", @@ -51,7 +52,7 @@ def _do_commit_weights( "commit_hash": commit_hash, }, ) - return subtensor.sign_and_send_extrinsic( + success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -63,60 +64,13 @@ def _do_commit_weights( raise_error=raise_error, ) - -def commit_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - commit_hash: str, - 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. - This function is a wrapper around the `do_commit_weights` method. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance used for blockchain interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. - netuid (int): The unique identifier of the subnet. - commit_hash (str): The hash of the neuron's weights to be committed. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. - - This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper - error handling and user interaction when required. - """ - - success, error_message = _do_commit_weights( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - commit_hash=commit_hash, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) - if success: - success_message = "Successfully committed weights." + success_message = "✅ [green]Successfully committed weights.[green]" logging.info(success_message) return True, success_message - logging.error(f"Failed to commit weights: {error_message}") - return False, error_message + logging.error(f"Failed to commit weights: {message}") + return False, message def _do_reveal_weights( diff --git a/migration.md b/migration.md index 30d832d9b3..a342f2f3b9 100644 --- a/migration.md +++ b/migration.md @@ -148,4 +148,5 @@ It must include: -[x] `._do_commit_reveal_v3` logic is included in the main code `.commit_reveal_v3_extrinsic` -[x] `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` --[x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` \ No newline at end of file +-[x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` +-[x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 2a00bf51ac..867590a2b7 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -270,152 +270,6 @@ async def test_set_weights_extrinsic_exception(subtensor, fake_wallet, mocker): assert message == "Unexpected error" -@pytest.mark.asyncio -async def test_do_commit_weights_success(subtensor, fake_wallet, mocker): - """Tests _do_commit_weights when the commit is successful.""" - # Preps - fake_netuid = 1 - fake_commit_hash = "test_hash" - - fake_call = mocker.AsyncMock() - fake_extrinsic = mocker.AsyncMock() - fake_response = mocker.Mock() - - async def fake_is_success(): - return True - - fake_response.is_success = fake_is_success() - fake_response.process_events = mocker.AsyncMock() - - mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) - mocker.patch.object( - subtensor.substrate, "create_signed_extrinsic", return_value=fake_extrinsic - ) - mocker.patch.object( - subtensor.substrate, "submit_extrinsic", return_value=fake_response - ) - - # Call - result, message = await async_weights._do_commit_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert result is True - assert message == "" - - -@pytest.mark.asyncio -async def test_do_commit_weights_failure(subtensor, fake_wallet, mocker): - """Tests _do_commit_weights when the commit fails.""" - # Preps - fake_netuid = 1 - fake_commit_hash = "test_hash" - - fake_call = mocker.AsyncMock() - fake_extrinsic = mocker.AsyncMock() - - async def fake_is_success(): - return False - - fake_response = mocker.Mock() - fake_response.is_success = fake_is_success() - fake_response.process_events = mocker.AsyncMock() - fake_response.error_message = mocker.AsyncMock(return_value="Error occurred")() - - mocked_format_error_message = mocker.Mock(return_value="Formatted error") - mocker.patch.object( - async_subtensor, "format_error_message", mocked_format_error_message - ) - - mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) - mocker.patch.object( - subtensor.substrate, "create_signed_extrinsic", return_value=fake_extrinsic - ) - mocker.patch.object( - subtensor.substrate, "submit_extrinsic", return_value=fake_response - ) - - # Call - result, message = await async_weights._do_commit_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert result is False - mocked_format_error_message.assert_called_once_with("Error occurred") - assert message == "Formatted error" - - -@pytest.mark.asyncio -async def test_do_commit_weights_no_waiting(subtensor, fake_wallet, mocker): - """Tests _do_commit_weights when not waiting for inclusion or finalization.""" - # Preps - fake_netuid = 1 - fake_commit_hash = "test_hash" - - fake_call = mocker.AsyncMock() - fake_extrinsic = mocker.AsyncMock() - fake_response = mocker.Mock() - - mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) - mocker.patch.object( - subtensor.substrate, "create_signed_extrinsic", return_value=fake_extrinsic - ) - mocker.patch.object( - subtensor.substrate, "submit_extrinsic", return_value=fake_response - ) - - # Call - result, message = await async_weights._do_commit_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - # Asserts - assert result is True - assert message == "Not waiting for finalization or inclusion." - - -@pytest.mark.asyncio -async def test_do_commit_weights_exception(subtensor, fake_wallet, mocker): - """Tests _do_commit_weights when an exception is raised.""" - # Preps - fake_netuid = 1 - fake_commit_hash = "test_hash" - - mocker.patch.object( - subtensor.substrate, - "compose_call", - side_effect=Exception("Unexpected exception"), - ) - - # Call - with pytest.raises(Exception, match="Unexpected exception"): - await async_weights._do_commit_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - @pytest.mark.asyncio async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): """Tests commit_weights_extrinsic when the commit is successful.""" @@ -423,8 +277,9 @@ async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): fake_netuid = 1 fake_commit_hash = "test_hash" - mocked_do_commit_weights = mocker.patch.object( - async_weights, "_do_commit_weights", return_value=(True, None) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, None) ) # Call @@ -438,15 +293,21 @@ async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): ) # Asserts - mocked_do_commit_weights.assert_called_once_with( - subtensor=subtensor, + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_weights", + call_params={"netuid": fake_netuid, "commit_hash": fake_commit_hash}, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, wait_for_inclusion=True, wait_for_finalization=True, + use_nonce=True, period=None, raise_error=False, + nonce_key="hotkey", + sign_with="hotkey", ) assert result is True assert message == "✅ [green]Successfully committed weights.[green]" @@ -459,8 +320,9 @@ async def test_commit_weights_extrinsic_failure(subtensor, fake_wallet, mocker): fake_netuid = 1 fake_commit_hash = "test_hash" - mocked_do_commit_weights = mocker.patch.object( - async_weights, "_do_commit_weights", return_value=(False, "Commit failed.") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(False, "Commit failed.") ) # Call @@ -474,15 +336,21 @@ async def test_commit_weights_extrinsic_failure(subtensor, fake_wallet, mocker): ) # Asserts - mocked_do_commit_weights.assert_called_once_with( - subtensor=subtensor, + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_weights", + call_params={"netuid": fake_netuid, "commit_hash": fake_commit_hash}, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, wait_for_inclusion=True, wait_for_finalization=True, + use_nonce=True, period=None, raise_error=False, + nonce_key="hotkey", + sign_with="hotkey", ) 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 4531a34ce0..84c5c64381 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -1,62 +1,9 @@ from bittensor.core.extrinsics.commit_weights import ( - _do_commit_weights, _do_reveal_weights, ) from bittensor.core.settings import version_as_int -def test_do_commit_weights(subtensor, fake_wallet, mocker): - """Successful _do_commit_weights call.""" - # Preps - netuid = 1 - commit_hash = "fake_commit_hash" - wait_for_inclusion = True - wait_for_finalization = True - - mocker.patch.object(subtensor, "sign_and_send_extrinsic", return_value=(False, "")) - mocker.patch.object(subtensor, "get_block_hash", return_value=1) - - mocked_format_error_message = mocker.Mock() - mocker.patch( - "bittensor.core.subtensor.format_error_message", - mocked_format_error_message, - ) - - # Call - result = _do_commit_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuid=netuid, - commit_hash=commit_hash, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - # Assertions - subtensor.substrate.compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="commit_weights", - call_params={ - "netuid": netuid, - "commit_hash": commit_hash, - }, - ) - - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=None, - nonce_key="hotkey", - sign_with="hotkey", - use_nonce=True, - raise_error=True, - ) - - assert result == (False, "") - - def test_do_reveal_weights(subtensor, fake_wallet, mocker): """Verifies that the `_do_reveal_weights` method interacts with the right substrate methods.""" # Preps From 8ef661b4517a457db03b97b776ad72f263f537c1 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 16:11:34 -0700 Subject: [PATCH 088/177] add `get_function_name` function --- bittensor/utils/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 57d528f76f..da2ea4fbaa 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -5,7 +5,7 @@ from collections import namedtuple from typing import Any, Literal, Union, Optional, TYPE_CHECKING from urllib.parse import urlparse - +import inspect import scalecodec from async_substrate_interface.utils import ( hex_to_bytes, @@ -479,3 +479,8 @@ def get_transfer_fn_params( else: call_function = "transfer_allow_death" return call_function, call_params + + +def get_function_name() -> str: + """Returns the name of the calling function.""" + return inspect.currentframe().f_back.f_code.co_name From ff4b3f734d61a4cb9e5cab1ee0e6a6ca132b1813 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 16:13:07 -0700 Subject: [PATCH 089/177] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` --- bittensor/core/extrinsics/asyncex/weights.py | 87 +++-------------- bittensor/core/extrinsics/commit_weights.py | 98 ++++---------------- 2 files changed, 35 insertions(+), 150 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 8a541f351b..4cc9ff1772 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -6,6 +6,7 @@ from numpy.typing import NDArray from bittensor.core.settings import version_as_int +from bittensor.utils import get_function_name from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_and_normalize_weights_and_uids @@ -79,12 +80,12 @@ async def commit_weights_extrinsic( return False, message -async def _do_reveal_weights( +async def reveal_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, uids: list[int], - values: list[int], + weights: list[int], salt: list[int], version_key: int, wait_for_inclusion: bool = False, @@ -93,16 +94,15 @@ async def _do_reveal_weights( raise_error: bool = False, ) -> tuple[bool, str]: """ - Internal method to send a transaction to the Bittensor blockchain, revealing the weights for a specific subnet. - This method constructs and submits the transaction, handling retries and blockchain communication. + Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. + This function is a wrapper around the `_do_reveal_weights` method. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain - interaction. + subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain interaction. wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. netuid (int): The unique identifier of the subnet. uids (list[int]): List of neuron UIDs for which weights are being revealed. - values (list[int]): List of weight values corresponding to each UID. + weights (list[int]): List of weight values corresponding to each UID. salt (list[int]): List of salt values corresponding to the hash function. version_key (int): Version key for compatibility with the network. wait_for_inclusion (bool): Waits for the transaction to be included in a block. @@ -117,8 +117,8 @@ async def _do_reveal_weights( `True` if the weight commitment is successful, `False` otherwise. `msg` is a string value describing the success or potential error. - This method ensures that the weight revelation is securely recorded on the Bittensor blockchain, providing - transparency and accountability for the neuron's weight distribution. + This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper + error handling and user interaction when required. """ call = await subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -126,12 +126,12 @@ async def _do_reveal_weights( call_params={ "netuid": netuid, "uids": uids, - "values": values, + "values": weights, "salt": salt, "version_key": version_key, }, ) - return await subtensor.sign_and_send_extrinsic( + success, message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -143,68 +143,11 @@ async def _do_reveal_weights( raise_error=raise_error, ) - -async def reveal_weights_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - uids: list[int], - weights: list[int], - salt: list[int], - version_key: int, - 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. - This function is a wrapper around the `_do_reveal_weights` method. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. - netuid (int): The unique identifier of the subnet. - uids (list[int]): List of neuron UIDs for which weights are being revealed. - weights (list[int]): List of weight values corresponding to each UID. - salt (list[int]): List of salt values corresponding to the hash function. - version_key (int): Version key for compatibility with the network. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. - - This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper - error handling and user interaction when required. - """ - success, error_message = await _do_reveal_weights( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - uids=uids, - values=weights, - salt=salt, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) - if success: - success_message = "Successfully revealed weights." - logging.info(success_message) - return True, success_message - - logging.error(f"Failed to reveal weights: {error_message}") - return False, error_message + logging.info(message) + else: + logging.error(f"{get_function_name}: {message}") + return success, message async def _do_set_weights( diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index 5a8ecb77bf..bd9fc38efd 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -2,6 +2,7 @@ from typing import TYPE_CHECKING, Optional +from bittensor.utils import get_function_name from bittensor.utils.btlogging import logging if TYPE_CHECKING: @@ -65,20 +66,18 @@ def commit_weights_extrinsic( ) if success: - success_message = "✅ [green]Successfully committed weights.[green]" - logging.info(success_message) - return True, success_message + logging.info(message) + else: + logging.error(f"{get_function_name()}: {message}") + return success, message - logging.error(f"Failed to commit weights: {message}") - return False, message - -def _do_reveal_weights( +def reveal_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, uids: list[int], - values: list[int], + weights: list[int], salt: list[int], version_key: int, wait_for_inclusion: bool = False, @@ -87,15 +86,15 @@ def _do_reveal_weights( raise_error: bool = False, ) -> tuple[bool, str]: """ - Internal method to send a transaction to the Bittensor blockchain, revealing the weights for a specific subnet. - This method constructs and submits the transaction, handling retries and blockchain communication. + Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. + This function is a wrapper around the `_do_reveal_weights` method. Args: subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance used for blockchain interaction. wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. netuid (int): The unique identifier of the subnet. uids (list[int]): List of neuron UIDs for which weights are being revealed. - values (list[int]): List of weight values corresponding to each UID. + weights (list[int]): List of weight values corresponding to each UID. salt (list[int]): List of salt values corresponding to the hash function. version_key (int): Version key for compatibility with the network. wait_for_inclusion (bool): Waits for the transaction to be included in a block. @@ -103,14 +102,15 @@ 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]: `True` if the weight commitment is successful, `False` otherwise. `msg` is a string value describing the success or potential error. - This method ensures that the weight revelation is securely recorded on the Bittensor blockchain, providing - transparency and accountability for the neuron's weight distribution. + This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper + error handling and user interaction when required. """ call = subtensor.substrate.compose_call( @@ -119,12 +119,13 @@ def _do_reveal_weights( call_params={ "netuid": netuid, "uids": uids, - "values": values, + "values": weights, "salt": salt, "version_key": version_key, }, ) - return subtensor.sign_and_send_extrinsic( + + success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -136,67 +137,8 @@ def _do_reveal_weights( raise_error=raise_error, ) - -def reveal_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - uids: list[int], - weights: list[int], - salt: list[int], - version_key: int, - 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. - This function is a wrapper around the `_do_reveal_weights` method. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance used for blockchain interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. - netuid (int): The unique identifier of the subnet. - uids (list[int]): List of neuron UIDs for which weights are being revealed. - weights (list[int]): List of weight values corresponding to each UID. - salt (list[int]): List of salt values corresponding to the hash function. - version_key (int): Version key for compatibility with the network. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. - - This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper - error handling and user interaction when required. - """ - - success, error_message = _do_reveal_weights( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - uids=uids, - values=weights, - salt=salt, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) - if success: - success_message = "Successfully revealed weights." - logging.info(success_message) - return True, success_message - - error_message = error_message - logging.error(f"Failed to reveal weights: {error_message}") - return False, error_message + logging.info(message) + else: + logging.error(f"{get_function_name()}: {message}") + return success, message From de56649147198d7d7ea5afd96692d3b40ce1da86 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 16:13:37 -0700 Subject: [PATCH 090/177] add test coverage for `bittensor.core.extrinsics.commit_weights` module --- migration.md | 3 +- .../extrinsics/test_commit_weights.py | 129 +++++++++++++----- 2 files changed, 97 insertions(+), 35 deletions(-) diff --git a/migration.md b/migration.md index a342f2f3b9..e81fb1b2ce 100644 --- a/migration.md +++ b/migration.md @@ -149,4 +149,5 @@ It must include: -[x] `._do_commit_reveal_v3` logic is included in the main code `.commit_reveal_v3_extrinsic` -[x] `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` -[x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` --[x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` \ No newline at end of file +-[x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` +-[x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index 84c5c64381..6b907a6646 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -1,58 +1,119 @@ +import pytest + from bittensor.core.extrinsics.commit_weights import ( - _do_reveal_weights, + commit_weights_extrinsic, + reveal_weights_extrinsic, ) -from bittensor.core.settings import version_as_int -def test_do_reveal_weights(subtensor, fake_wallet, mocker): - """Verifies that the `_do_reveal_weights` method interacts with the right substrate methods.""" +@pytest.mark.parametrize( + "sign_and_send_return", + [ + (True, "Success"), + (False, "Failure"), + ], + ids=["success", "failure"], +) +def test_commit_weights_extrinsic(subtensor, fake_wallet, mocker, sign_and_send_return): + """Tests `commit_weights_extrinsic` calls proper methods.""" # Preps - fake_wallet.hotkey.ss58_address = "hotkey" + fake_netuid = 1 + fake_weights = mocker.Mock() + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=sign_and_send_return + ) + + # Call + result = commit_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + commit_hash=fake_weights, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + raise_error=False, + ) + + # Asserts + mocked_compose_call.assert_called_once_with( + call_module="SubtensorModule", + call_function="commit_weights", + call_params={"netuid": fake_netuid, "commit_hash": fake_weights}, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + nonce_key="hotkey", + period=None, + raise_error=False, + sign_with="hotkey", + use_nonce=True, + ) + assert result == sign_and_send_return + - netuid = 1 - uids = [1, 2, 3, 4] - values = [1, 2, 3, 4] - salt = [4, 2, 2, 1] - wait_for_inclusion = True - wait_for_finalization = True +@pytest.mark.parametrize( + "sign_and_send_return", + [ + (True, "Success"), + (False, "Failure"), + ], + ids=["success", "failure"], +) +def test_reveal_weights_extrinsic(subtensor, fake_wallet, mocker, sign_and_send_return): + """Tests `reveal_weights_extrinsic` calls proper methods.""" + # Preps + fake_netuid = 1 + fake_uids = mocker.Mock() + fake_weights = mocker.Mock() + fake_salt = mocker.Mock() + fake_version_key = mocker.Mock() - mocker.patch.object(subtensor, "sign_and_send_extrinsic") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=sign_and_send_return + ) # Call - result = _do_reveal_weights( + result = reveal_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, - netuid=netuid, - uids=uids, - values=values, - salt=salt, - version_key=version_as_int, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, + netuid=fake_netuid, + uids=fake_uids, + weights=fake_weights, + salt=fake_salt, + version_key=fake_version_key, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + raise_error=False, ) # Asserts - subtensor.substrate.compose_call.assert_called_once_with( + mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="reveal_weights", call_params={ - "netuid": netuid, - "uids": uids, - "values": values, - "salt": salt, - "version_key": version_as_int, + "netuid": fake_netuid, + "uids": fake_uids, + "values": fake_weights, + "salt": fake_salt, + "version_key": fake_version_key, }, ) - - subtensor.sign_and_send_extrinsic( - call=subtensor.substrate.compose_call.return_value, + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=None, + wait_for_inclusion=True, + wait_for_finalization=True, nonce_key="hotkey", + period=None, + raise_error=False, sign_with="hotkey", use_nonce=True, ) - - assert result == subtensor.sign_and_send_extrinsic.return_value + assert result == sign_and_send_return From 63fe1753351339165de44d7830b7f2ec9cf48b28 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:10:26 -0700 Subject: [PATCH 091/177] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` --- bittensor/core/extrinsics/asyncex/weights.py | 141 ++++++------------- bittensor/core/extrinsics/set_weights.py | 110 ++++----------- migration.md | 3 +- 3 files changed, 69 insertions(+), 185 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 4cc9ff1772..70bb2bd0ee 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -1,14 +1,16 @@ -"""This module provides sync functionality for working with weights in the Bittensor network.""" +"""This module provides async functionality for working with weights in the Bittensor network.""" from typing import Union, TYPE_CHECKING, Optional import numpy as np from numpy.typing import NDArray -from bittensor.core.settings import version_as_int from bittensor.utils import get_function_name from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import convert_and_normalize_weights_and_uids +from bittensor.utils.weight_utils import ( + convert_and_normalize_weights_and_uids, + convert_uids_and_weights, +) if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -72,12 +74,10 @@ async def commit_weights_extrinsic( ) if success: - success_message = "✅ [green]Successfully committed weights.[green]" - logging.info(success_message) - return True, success_message - - logging.error(f"Failed to commit weights: {message}") - return False, message + logging.info(message) + else: + logging.error(f"{get_function_name}: {message}") + return success, message async def reveal_weights_extrinsic( @@ -150,72 +150,6 @@ async def reveal_weights_extrinsic( return success, message -async def _do_set_weights( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - uids: list[int], - vals: list[int], - version_key: int = version_as_int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = None, -) -> tuple[bool, str]: # (success, error_message) - """ - Internal method to send a transaction to the Bittensor blockchain, setting weights for specified neurons. This - method constructs and submits the transaction, handling retries and blockchain communication. - - Args: - subtensor (subtensor.core.async_subtensor.AsyncSubtensor): Async Subtensor instance. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron setting the weights. - uids (List[int]): List of neuron UIDs for which weights are being set. - vals (List[int]): List of weight values corresponding to each UID. - netuid (int): Unique identifier for the network. - version_key (int, optional): Version key for compatibility with the network. - wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. - wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. - 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. - - Returns: - tuple[bool, str]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. - - This method is vital for the dynamic weighting mechanism in Bittensor, where neurons adjust their - trust in other neurons based on observed performance and contributions. - """ - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_weights", - call_params={ - "dests": uids, - "weights": vals, - "netuid": netuid, - "version_key": version_key, - }, - ) - success, message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - use_nonce=True, - nonce_key="hotkey", - sign_with="hotkey", - ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True, "Not waiting for finalization or inclusion." - - if success: - return success, "Successfully set weights." - return success, message - - async def set_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", @@ -226,6 +160,7 @@ async def set_weights_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = 8, + raise_error: bool = False, ) -> tuple[bool, str]: """Sets the given weights and values on chain for a given wallet hotkey account. @@ -244,12 +179,17 @@ async def set_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]: `True` if the weight commitment is successful, `False` otherwise. `msg` is a string value describing the success or potential error. """ + # Convert types. + uids, weights = convert_uids_and_weights(uids, weights) + + # Reformat and normalize. weight_uids, weight_vals = convert_and_normalize_weights_and_uids(uids, weights) logging.info( @@ -257,30 +197,31 @@ async def set_weights_extrinsic( f"[blue]{subtensor.network}[/blue] " f"[magenta]...[/magenta]" ) - try: - success, message = await _do_set_weights( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - uids=weight_uids, - vals=weight_vals, - version_key=version_key, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - period=period, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True, message - - if success is True: - message = "Successfully set weights and Finalized." - logging.success(f":white_heavy_check_mark: [green]{message}[/green]") - return True, message - logging.error(f"[red]Failed[/red] set weights. Error: {message}") - return False, message + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": weight_uids, + "weights": weight_vals, + "netuid": netuid, + "version_key": version_key, + }, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + raise_error=raise_error, + ) - except Exception as error: - logging.error(f":cross_mark: [red]Failed[/red] set weights. Error: {error}") - return False, str(error) + if success: + logging.info(message) + else: + logging.error(f"{get_function_name}: {message}") + return success, message diff --git a/bittensor/core/extrinsics/set_weights.py b/bittensor/core/extrinsics/set_weights.py index 039dbdf837..2a10d23139 100644 --- a/bittensor/core/extrinsics/set_weights.py +++ b/bittensor/core/extrinsics/set_weights.py @@ -6,6 +6,7 @@ from numpy.typing import NDArray from bittensor.core.settings import version_as_int +from bittensor.utils import get_function_name from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -18,65 +19,6 @@ from bittensor.utils.registration import torch -def _do_set_weights( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - uids: list[int], - vals: list[int], - version_key: int = version_as_int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = None, -) -> tuple[bool, str]: - """ - Internal method to send a transaction to the Bittensor blockchain, setting weights - for specified neurons. This method constructs and submits the transaction, handling - retries and blockchain communication. - - Args: - subtensor (subtensor.core.subtensor.Subtensor): Subtensor instance. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron setting the weights. - uids (List[int]): List of neuron UIDs for which weights are being set. - vals (List[int]): List of weight values corresponding to each UID. - netuid (int): Unique identifier for the network. - version_key (int, optional): Version key for compatibility with the network. - wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. - wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. - 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. - - Returns: - Tuple[bool, str]: A tuple containing a success flag and an optional error message. - - This method is vital for the dynamic weighting mechanism in Bittensor, where neurons adjust their - trust in other neurons based on observed performance and contributions. - """ - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_weights", - call_params={ - "dests": uids, - "weights": vals, - "netuid": netuid, - "version_key": version_key, - }, - ) - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - use_nonce=True, - nonce_key="hotkey", - sign_with="hotkey", - ) - return success, message - - def set_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -121,30 +63,30 @@ def set_weights_extrinsic( f"[blue]{subtensor.network}[/blue] " f"[magenta]...[/magenta]" ) - try: - success, message = _do_set_weights( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - uids=weight_uids, - vals=weight_vals, - version_key=version_key, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - period=period, - ) - if not wait_for_finalization and not wait_for_inclusion: - return True, message - - if success is True: - message = "Successfully set weights and Finalized." - logging.success(f":white_heavy_check_mark: [green]{message}[/green]") - return True, message - - logging.error(f"[red]Failed[/red] set weights. Error: {message}") - return False, message + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": weight_uids, + "weights": weight_vals, + "netuid": netuid, + "version_key": version_key, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + ) - except Exception as error: - logging.error(f":cross_mark: [red]Failed[/red] set weights. Error: {error}") - return False, str(error) + if success: + logging.info(message) + else: + logging.error(f"{get_function_name}: {message}") + return success, message diff --git a/migration.md b/migration.md index e81fb1b2ce..05be753af2 100644 --- a/migration.md +++ b/migration.md @@ -150,4 +150,5 @@ It must include: -[x] `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` -[x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` -[x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` --[x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` \ No newline at end of file +-[x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` +-[x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` \ No newline at end of file From b398a3d0e846ffe0c552bca496c60cc8afa54d48 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:10:38 -0700 Subject: [PATCH 092/177] fix texts --- .../extrinsics/asyncex/test_weights.py | 305 ++++++++---------- .../unit_tests/extrinsics/test_set_weights.py | 108 +------ 2 files changed, 140 insertions(+), 273 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 867590a2b7..035ac8048f 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -3,137 +3,6 @@ from bittensor.core.extrinsics.asyncex import weights as async_weights -@pytest.mark.asyncio -async def test_do_set_weights_success(subtensor, fake_wallet, mocker): - """Tests _do_set_weights when weights are set successfully.""" - # Preps - fake_uids = [1, 2, 3] - fake_vals = [100, 200, 300] - fake_netuid = 0 - - fake_call = mocker.AsyncMock() - fake_extrinsic = mocker.AsyncMock() - fake_response = mocker.Mock() - - async def fake_is_success(): - return True - - fake_response.is_success = fake_is_success() - - fake_response.process_events = mocker.AsyncMock() - - mocker.patch.object(subtensor.substrate, "compose_call", fake_call) - mocker.patch.object(subtensor.substrate, "create_signed_extrinsic", fake_extrinsic) - mocker.patch.object( - subtensor.substrate, - "submit_extrinsic", - mocker.AsyncMock(return_value=fake_response), - ) - - # Call - result, message = await async_weights._do_set_weights( - subtensor=subtensor, - wallet=fake_wallet, - uids=fake_uids, - vals=fake_vals, - netuid=fake_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert result is True - assert message == "Successfully set weights." - - -@pytest.mark.asyncio -async def test_do_set_weights_failure(subtensor, fake_wallet, mocker): - """Tests _do_set_weights when setting weights fails.""" - # Preps - fake_uids = [1, 2, 3] - fake_vals = [100, 200, 300] - fake_netuid = 0 - - fake_call = mocker.AsyncMock() - fake_extrinsic = mocker.AsyncMock() - - async def fake_is_success(): - return False - - fake_response = mocker.Mock() - fake_response.is_success = fake_is_success() - - fake_response.process_events = mocker.AsyncMock() - - fake_response.error_message = mocker.AsyncMock(return_value="Error occurred")() - fake_response.process_events = mocker.AsyncMock() - - mocked_format_error_message = mocker.Mock() - mocker.patch.object( - async_subtensor, "format_error_message", mocked_format_error_message - ) - - mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) - mocker.patch.object( - subtensor.substrate, "create_signed_extrinsic", return_value=fake_extrinsic - ) - mocker.patch.object( - subtensor.substrate, "submit_extrinsic", return_value=fake_response - ) - - # Call - result, message = await async_weights._do_set_weights( - subtensor=subtensor, - wallet=fake_wallet, - uids=fake_uids, - vals=fake_vals, - netuid=fake_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert result is False - mocked_format_error_message.assert_called_once_with("Error occurred") - assert message == mocked_format_error_message.return_value - - -@pytest.mark.asyncio -async def test_do_set_weights_no_waiting(subtensor, fake_wallet, mocker): - """Tests _do_set_weights when not waiting for inclusion or finalization.""" - # Preps - fake_uids = [1, 2, 3] - fake_vals = [100, 200, 300] - fake_netuid = 0 - - fake_call = mocker.AsyncMock() - fake_extrinsic = mocker.AsyncMock() - fake_response = mocker.Mock() - - mocker.patch.object(subtensor.substrate, "compose_call", fake_call) - mocker.patch.object(subtensor.substrate, "create_signed_extrinsic", fake_extrinsic) - mocker.patch.object( - subtensor.substrate, - "submit_extrinsic", - mocker.AsyncMock(return_value=fake_response), - ) - - # Call - result, message = await async_weights._do_set_weights( - subtensor=subtensor, - wallet=fake_wallet, - uids=fake_uids, - vals=fake_vals, - netuid=fake_netuid, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - # Asserts - assert result is True - assert message == "Not waiting for finalization or inclusion." - - @pytest.mark.asyncio async def test_set_weights_extrinsic_success_with_finalization( subtensor, fake_wallet, mocker @@ -144,13 +13,21 @@ async def test_set_weights_extrinsic_success_with_finalization( fake_uids = mocker.Mock() fake_weights = mocker.Mock() - mocked_do_set_weights = mocker.patch.object( - async_weights, "_do_set_weights", return_value=(True, "") + mocked_convert_types = mocker.patch.object( + async_weights, + "convert_uids_and_weights", + return_value=(mocker.Mock(), mocker.Mock()), ) - mocker_converter = mocker.patch.object( - async_weights, "convert_and_normalize_weights_and_uids" + mocker_converter_normalize = mocker.patch.object( + async_weights, + "convert_and_normalize_weights_and_uids", + return_value=(mocker.Mock(), mocker.Mock()), + ) + + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") ) - mocker_converter.return_value = (mocker.Mock(), mocker.Mock()) # Call result, message = await async_weights.set_weights_extrinsic( @@ -164,21 +41,33 @@ async def test_set_weights_extrinsic_success_with_finalization( ) # Asserts - mocker_converter.assert_called_once_with(fake_uids, fake_weights) - - mocked_do_set_weights.assert_called_once_with( - subtensor=subtensor, + mocked_convert_types.assert_called_once_with(fake_uids, fake_weights) + mocker_converter_normalize.assert_called_once_with( + mocked_convert_types.return_value[0], mocked_convert_types.return_value[1] + ) + mocked_compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": mocker_converter_normalize.return_value[0], + "weights": mocker_converter_normalize.return_value[1], + "netuid": fake_netuid, + "version_key": 0, + }, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuid=fake_netuid, - uids=mocker_converter.return_value[0], - vals=mocker_converter.return_value[1], - version_key=0, wait_for_finalization=True, wait_for_inclusion=True, period=8, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + raise_error=False, ) assert result is True - assert message == "Successfully set weights and Finalized." + assert message == "" @pytest.mark.asyncio @@ -189,9 +78,21 @@ async def test_set_weights_extrinsic_no_waiting(subtensor, fake_wallet, mocker): fake_uids = [1, 2, 3] fake_weights = [0.1, 0.2, 0.7] - mocked_do_set_weights = mocker.patch.object( + mocked_convert_types = mocker.patch.object( + async_weights, + "convert_uids_and_weights", + return_value=(mocker.Mock(), mocker.Mock()), + ) + mocker_converter_normalize = mocker.patch.object( async_weights, - "_do_set_weights", + "convert_and_normalize_weights_and_uids", + return_value=(mocker.Mock(), mocker.Mock()), + ) + + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", return_value=(True, "Not waiting for finalization or inclusion."), ) @@ -207,7 +108,31 @@ async def test_set_weights_extrinsic_no_waiting(subtensor, fake_wallet, mocker): ) # Asserts - mocked_do_set_weights.assert_called_once() + mocked_convert_types.assert_called_once_with(fake_uids, fake_weights) + mocker_converter_normalize.assert_called_once_with( + mocked_convert_types.return_value[0], mocked_convert_types.return_value[1] + ) + mocked_compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": mocker_converter_normalize.return_value[0], + "weights": mocker_converter_normalize.return_value[1], + "netuid": fake_netuid, + "version_key": 0, + }, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_finalization=False, + wait_for_inclusion=False, + period=8, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + raise_error=False, + ) assert result is True assert message == "Not waiting for finalization or inclusion." @@ -220,8 +145,20 @@ async def test_set_weights_extrinsic_failure(subtensor, fake_wallet, mocker): fake_uids = [1, 2, 3] fake_weights = [0.1, 0.2, 0.7] - mocked_do_set_weights = mocker.patch.object( - async_weights, "_do_set_weights", return_value=(False, "Test error message") + mocked_convert_types = mocker.patch.object( + async_weights, + "convert_uids_and_weights", + return_value=(mocker.Mock(), mocker.Mock()), + ) + mocker_converter_normalize = mocker.patch.object( + async_weights, + "convert_and_normalize_weights_and_uids", + return_value=(mocker.Mock(), mocker.Mock()), + ) + + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(False, "Test error message") ) # Call @@ -236,7 +173,31 @@ async def test_set_weights_extrinsic_failure(subtensor, fake_wallet, mocker): ) # Asserts - mocked_do_set_weights.assert_called_once() + mocked_convert_types.assert_called_once_with(fake_uids, fake_weights) + mocker_converter_normalize.assert_called_once_with( + mocked_convert_types.return_value[0], mocked_convert_types.return_value[1] + ) + mocked_compose_call.assert_awaited_once_with( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": mocker_converter_normalize.return_value[0], + "weights": mocker_converter_normalize.return_value[1], + "netuid": fake_netuid, + "version_key": 0, + }, + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_finalization=True, + wait_for_inclusion=True, + period=8, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + raise_error=False, + ) assert result is False assert message == "Test error message" @@ -249,25 +210,33 @@ async def test_set_weights_extrinsic_exception(subtensor, fake_wallet, mocker): fake_uids = [1, 2, 3] fake_weights = [0.1, 0.2, 0.7] - mocked_do_set_weights = mocker.patch.object( - async_weights, "_do_set_weights", side_effect=Exception("Unexpected error") + mocker.patch.object( + async_weights, + "convert_uids_and_weights", + return_value=(mocker.Mock(), mocker.Mock()), + ) + mocker.patch.object( + async_weights, + "convert_and_normalize_weights_and_uids", + return_value=(mocker.Mock(), mocker.Mock()), ) - # Call - result, message = await async_weights.set_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=True, - wait_for_finalization=True, + mocker.patch.object(subtensor.substrate, "compose_call") + mocker.patch.object( + subtensor, "sign_and_send_extrinsic", side_effect=Exception("Unexpected error") ) - # Asserts - mocked_do_set_weights.assert_called_once() - assert result is False - assert message == "Unexpected error" + # Call + with pytest.raises(Exception): + await async_weights.set_weights_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + uids=fake_uids, + weights=fake_weights, + wait_for_inclusion=True, + wait_for_finalization=True, + ) @pytest.mark.asyncio @@ -279,7 +248,7 @@ async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, None) + subtensor, "sign_and_send_extrinsic", return_value=(True, "") ) # Call @@ -310,7 +279,7 @@ async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): sign_with="hotkey", ) assert result is True - assert message == "✅ [green]Successfully committed weights.[green]" + assert message == "" @pytest.mark.asyncio diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py index 6a47809b43..b4e01ede67 100644 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ b/tests/unit_tests/extrinsics/test_set_weights.py @@ -1,13 +1,10 @@ from unittest.mock import MagicMock, patch import pytest -# import torch from bittensor.core.extrinsics.set_weights import ( - _do_set_weights, set_weights_extrinsic, ) -from bittensor.core.settings import version_as_int from bittensor.core.subtensor import Subtensor @@ -74,8 +71,9 @@ def test_set_weights_extrinsic( "bittensor.utils.weight_utils.convert_weights_and_uids_for_emit", return_value=(uids, weights), ), - patch( - "bittensor.core.extrinsics.set_weights._do_set_weights", + patch.object( + mock_subtensor, + "sign_and_send_extrinsic", return_value=(expected_success, expected_message), ), ): @@ -92,103 +90,3 @@ def test_set_weights_extrinsic( assert result == expected_success, f"Test {expected_message} failed." assert message == expected_message, f"Test {expected_message} failed." - - -def test_do_set_weights_is_success(mock_subtensor, fake_wallet, mocker): - """Successful _do_set_weights call.""" - # Prep - fake_uids = [1, 2, 3] - fake_vals = [4, 5, 6] - fake_netuid = 1 - fake_wait_for_inclusion = True - fake_wait_for_finalization = True - - mock_subtensor.substrate.submit_extrinsic.return_value.is_success = True - mocker.patch.object( - mock_subtensor, "sign_and_send_extrinsic", return_value=(True, "") - ) - - # Call - result = _do_set_weights( - subtensor=mock_subtensor, - wallet=fake_wallet, - uids=fake_uids, - vals=fake_vals, - netuid=fake_netuid, - version_key=version_as_int, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - ) - - # Asserts - mock_subtensor.substrate.compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="set_weights", - call_params={ - "dests": fake_uids, - "weights": fake_vals, - "netuid": fake_netuid, - "version_key": version_as_int, - }, - ) - - mock_subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=mock_subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - nonce_key="hotkey", - sign_with="hotkey", - use_nonce=True, - period=None, - ) - assert result == (True, "") - - -def test_do_set_weights_no_waits(mock_subtensor, fake_wallet, mocker): - """Successful _do_set_weights call without wait flags for fake_wait_for_inclusion and fake_wait_for_finalization.""" - # Prep - fake_uids = [1, 2, 3] - fake_vals = [4, 5, 6] - fake_netuid = 1 - fake_wait_for_inclusion = False - fake_wait_for_finalization = False - - mocker.patch.object( - mock_subtensor, - "sign_and_send_extrinsic", - return_value=(True, "Not waiting for finalization or inclusion."), - ) - - # Call - result = _do_set_weights( - subtensor=mock_subtensor, - wallet=fake_wallet, - uids=fake_uids, - vals=fake_vals, - netuid=fake_netuid, - version_key=version_as_int, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - ) - - # Asserts - mock_subtensor.substrate.compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="set_weights", - call_params={ - "dests": fake_uids, - "weights": fake_vals, - "netuid": fake_netuid, - "version_key": version_as_int, - }, - ) - - mock_subtensor.sign_and_send_extrinsic( - call=mock_subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - period=None, - ) - assert result == (True, "Not waiting for finalization or inclusion.") From 1482fb823b1c8c421831abcb801f6c1b65c5eb4f Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:15:49 -0700 Subject: [PATCH 093/177] fix `version_as_int` argument --- bittensor/core/extrinsics/asyncex/weights.py | 3 ++- bittensor/core/extrinsics/set_weights.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 70bb2bd0ee..d2ed19a1c3 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -5,6 +5,7 @@ import numpy as np from numpy.typing import NDArray +from bittensor.core.settings import version_as_int from bittensor.utils import get_function_name from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( @@ -156,7 +157,7 @@ async def set_weights_extrinsic( netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], - version_key: int = 0, + version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = 8, diff --git a/bittensor/core/extrinsics/set_weights.py b/bittensor/core/extrinsics/set_weights.py index 2a10d23139..e34091e1c9 100644 --- a/bittensor/core/extrinsics/set_weights.py +++ b/bittensor/core/extrinsics/set_weights.py @@ -25,7 +25,7 @@ def set_weights_extrinsic( netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], - version_key: int = 0, + version_key: int = version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = 8, From a5ebb1b1976803133d12d2bec5a7e690f4f57ddf Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:19:07 -0700 Subject: [PATCH 094/177] fix `version_as_int` argument in tests --- tests/unit_tests/extrinsics/asyncex/test_weights.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 035ac8048f..dc1bbdc8f0 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -1,6 +1,7 @@ import pytest from bittensor.core import async_subtensor from bittensor.core.extrinsics.asyncex import weights as async_weights +from bittensor.core.settings import version_as_int @pytest.mark.asyncio @@ -52,7 +53,7 @@ async def test_set_weights_extrinsic_success_with_finalization( "dests": mocker_converter_normalize.return_value[0], "weights": mocker_converter_normalize.return_value[1], "netuid": fake_netuid, - "version_key": 0, + "version_key": version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( @@ -119,7 +120,7 @@ async def test_set_weights_extrinsic_no_waiting(subtensor, fake_wallet, mocker): "dests": mocker_converter_normalize.return_value[0], "weights": mocker_converter_normalize.return_value[1], "netuid": fake_netuid, - "version_key": 0, + "version_key": version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( @@ -184,7 +185,7 @@ async def test_set_weights_extrinsic_failure(subtensor, fake_wallet, mocker): "dests": mocker_converter_normalize.return_value[0], "weights": mocker_converter_normalize.return_value[1], "netuid": fake_netuid, - "version_key": 0, + "version_key": version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( From b61e2a52b93dd119754ecabaa98af9322fbf9321 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:27:43 -0700 Subject: [PATCH 095/177] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` --- bittensor/core/extrinsics/commit_weights.py | 86 ++++++++++++++++- bittensor/core/extrinsics/set_weights.py | 92 ------------------- bittensor/core/subtensor.py | 2 +- migration.md | 3 +- .../unit_tests/extrinsics/test_set_weights.py | 2 +- 5 files changed, 88 insertions(+), 97 deletions(-) delete mode 100644 bittensor/core/extrinsics/set_weights.py diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/commit_weights.py index bd9fc38efd..071e5036b2 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -1,13 +1,22 @@ """Module sync commit weights and reveal weights extrinsic.""" -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Union +import numpy as np +from numpy.typing import NDArray + +from bittensor.core.settings import version_as_int from bittensor.utils import get_function_name from bittensor.utils.btlogging import logging +from bittensor.utils.weight_utils import ( + convert_and_normalize_weights_and_uids, + convert_uids_and_weights, +) if TYPE_CHECKING: - from bittensor_wallet import Wallet from bittensor.core.subtensor import Subtensor + from bittensor_wallet import Wallet + from bittensor.utils.registration import torch def commit_weights_extrinsic( @@ -142,3 +151,76 @@ def reveal_weights_extrinsic( else: logging.error(f"{get_function_name()}: {message}") return success, message + + +def set_weights_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + uids: Union[NDArray[np.int64], "torch.LongTensor", list], + weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + version_key: int = version_as_int, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, + period: Optional[int] = 8, +) -> tuple[bool, str]: + """Sets the given weights and values on a chain for a wallet hotkey account. + + Args: + subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Bittensor subtensor object. + wallet (bittensor_wallet.Wallet): Bittensor wallet object. + netuid (int): The ``netuid`` of the subnet to set weights for. + uids (Union[NDArray[np.int64], torch.LongTensor, list]): The ``uint64`` uids of destination neurons. + weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The weights to set. These must be ``float`` s + and correspond to the passed ``uid`` s. + version_key (int): The version key of the validator. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or + returns ``False`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning + ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. + 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. + + Returns: + success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for + finalization / inclusion, the response is ``True``. + """ + # Convert types. + uids, weights = convert_uids_and_weights(uids, weights) + + # Reformat and normalize. + weight_uids, weight_vals = convert_and_normalize_weights_and_uids(uids, weights) + + logging.info( + f":satellite: [magenta]Setting weights on [/magenta]" + f"[blue]{subtensor.network}[/blue] " + f"[magenta]...[/magenta]" + ) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_weights", + call_params={ + "dests": weight_uids, + "weights": weight_vals, + "netuid": netuid, + "version_key": version_key, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + use_nonce=True, + nonce_key="hotkey", + sign_with="hotkey", + ) + + if success: + logging.info(message) + else: + logging.error(f"{get_function_name}: {message}") + return success, message diff --git a/bittensor/core/extrinsics/set_weights.py b/bittensor/core/extrinsics/set_weights.py deleted file mode 100644 index e34091e1c9..0000000000 --- a/bittensor/core/extrinsics/set_weights.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Module sync setting weights extrinsic.""" - -from typing import Union, TYPE_CHECKING, Optional - -import numpy as np -from numpy.typing import NDArray - -from bittensor.core.settings import version_as_int -from bittensor.utils import get_function_name -from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import ( - convert_and_normalize_weights_and_uids, - convert_uids_and_weights, -) - -if TYPE_CHECKING: - from bittensor.core.subtensor import Subtensor - from bittensor_wallet import Wallet - from bittensor.utils.registration import torch - - -def set_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - uids: Union[NDArray[np.int64], "torch.LongTensor", list], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list], - version_key: int = version_as_int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = 8, -) -> tuple[bool, str]: - """Sets the given weights and values on a chain for a wallet hotkey account. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Bittensor subtensor object. - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - netuid (int): The ``netuid`` of the subnet to set weights for. - uids (Union[NDArray[np.int64], torch.LongTensor, list]): The ``uint64`` uids of destination neurons. - weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The weights to set. These must be ``float`` s - and correspond to the passed ``uid`` s. - version_key (int): The version key of the validator. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. - 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. - - Returns: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``True``. - """ - # Convert types. - uids, weights = convert_uids_and_weights(uids, weights) - - # Reformat and normalize. - weight_uids, weight_vals = convert_and_normalize_weights_and_uids(uids, weights) - - logging.info( - f":satellite: [magenta]Setting weights on [/magenta]" - f"[blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_weights", - call_params={ - "dests": weight_uids, - "weights": weight_vals, - "netuid": netuid, - "version_key": version_key, - }, - ) - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - use_nonce=True, - nonce_key="hotkey", - sign_with="hotkey", - ) - - if success: - logging.info(message) - else: - logging.error(f"{get_function_name}: {message}") - return success, message diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f9a82faa36..0095b0414d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -75,7 +75,7 @@ get_metadata, serve_axon_extrinsic, ) -from bittensor.core.extrinsics.set_weights import set_weights_extrinsic +from bittensor.core.extrinsics.commit_weights import set_weights_extrinsic from bittensor.core.extrinsics.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, diff --git a/migration.md b/migration.md index 05be753af2..fd60682da9 100644 --- a/migration.md +++ b/migration.md @@ -151,4 +151,5 @@ It must include: -[x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` -[x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` -[x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` --[x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` \ No newline at end of file +-[x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` +-[x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py index b4e01ede67..e41b8f6ba7 100644 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ b/tests/unit_tests/extrinsics/test_set_weights.py @@ -2,7 +2,7 @@ import pytest -from bittensor.core.extrinsics.set_weights import ( +from bittensor.core.extrinsics.commit_weights import ( set_weights_extrinsic, ) from bittensor.core.subtensor import Subtensor From 98b18f90cee8c9a8b2f5f962a62faa40f5cd6fd8 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:39:09 -0700 Subject: [PATCH 096/177] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` + cleanup --- bittensor/core/extrinsics/asyncex/weights.py | 70 ++++++++--------- .../{commit_weights.py => weights.py} | 77 ++++++++++--------- bittensor/core/subtensor.py | 4 +- migration.md | 1 + .../extrinsics/test_commit_weights.py | 2 +- .../unit_tests/extrinsics/test_set_weights.py | 2 +- 6 files changed, 79 insertions(+), 77 deletions(-) rename bittensor/core/extrinsics/{commit_weights.py => weights.py} (62%) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index d2ed19a1c3..3acd1e1ff7 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -34,16 +34,15 @@ async def commit_weights_extrinsic( This function is a wrapper around the `do_commit_weights` method. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain - interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. - netuid (int): The unique identifier of the subnet. - commit_hash (str): The hash of the neuron's weights to be committed. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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. + subtensor: The subtensor instance used for blockchain interaction. + wallet: The wallet associated with the neuron committing the weights. + netuid: The unique identifier of the subnet. + commit_hash: The hash of the neuron's weights to be committed. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + period: 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: @@ -99,18 +98,18 @@ async def reveal_weights_extrinsic( This function is a wrapper around the `_do_reveal_weights` method. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for blockchain interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. - netuid (int): The unique identifier of the subnet. - uids (list[int]): List of neuron UIDs for which weights are being revealed. - weights (list[int]): List of weight values corresponding to each UID. - salt (list[int]): List of salt values corresponding to the hash function. - version_key (int): Version key for compatibility with the network. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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. + subtensor: The subtensor instance used for blockchain interaction. + wallet: The wallet associated with the neuron revealing the weights. + netuid: The unique identifier of the subnet. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + salt: List of salt values corresponding to the hash function. + version_key: Version key for compatibility with the network. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + period: 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: @@ -166,20 +165,19 @@ async def set_weights_extrinsic( """Sets the given weights and values on chain for a given wallet hotkey account. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Bittensor subtensor object. - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - netuid (int): The ``netuid`` of the subnet to set weights for. - uids (Union[NDArray[np.int64], torch.LongTensor, list]): The ``uint64`` uids of destination neurons. - weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The weights to set. These must be ``float`` s and - correspond to the passed ``uid`` s. - version_key (int): The version key of the validator. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. - 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. + subtensor: Bittensor subtensor object. + wallet: Bittensor wallet object. + netuid: The ``netuid`` of the subnet to set weights for. + uids: The ``uint64`` uids of destination neurons. + weights: The weights to set. These must be ``float``s and correspond to the passed ``uid``s. + version_key: The version key of the validator. + wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns + ``False`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, + or returns ``False`` if the extrinsic fails to be finalized within the timeout. + period: 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: diff --git a/bittensor/core/extrinsics/commit_weights.py b/bittensor/core/extrinsics/weights.py similarity index 62% rename from bittensor/core/extrinsics/commit_weights.py rename to bittensor/core/extrinsics/weights.py index 071e5036b2..ddbf21b5d0 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -34,15 +34,15 @@ def commit_weights_extrinsic( This function is a wrapper around the `do_commit_weights` method. Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance used for blockchain interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. - netuid (int): The unique identifier of the subnet. - commit_hash (str): The hash of the neuron's weights to be committed. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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. + subtensor: The subtensor instance used for blockchain interaction. + wallet: The wallet associated with the neuron committing the weights. + netuid: The unique identifier of the subnet. + commit_hash: The hash of the neuron's weights to be committed. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + period: 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: @@ -99,18 +99,18 @@ def reveal_weights_extrinsic( This function is a wrapper around the `_do_reveal_weights` method. Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance used for blockchain interaction. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. - netuid (int): The unique identifier of the subnet. - uids (list[int]): List of neuron UIDs for which weights are being revealed. - weights (list[int]): List of weight values corresponding to each UID. - salt (list[int]): List of salt values corresponding to the hash function. - version_key (int): Version key for compatibility with the network. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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. + subtensor: The subtensor instance used for blockchain interaction. + wallet: The wallet associated with the neuron revealing the weights. + netuid: The unique identifier of the subnet. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + salt: List of salt values corresponding to the hash function. + version_key: Version key for compatibility with the network. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + period: 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: @@ -163,28 +163,30 @@ def set_weights_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, period: Optional[int] = 8, + raise_error: bool = False, ) -> tuple[bool, str]: """Sets the given weights and values on a chain for a wallet hotkey account. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Bittensor subtensor object. - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - netuid (int): The ``netuid`` of the subnet to set weights for. - uids (Union[NDArray[np.int64], torch.LongTensor, list]): The ``uint64`` uids of destination neurons. - weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The weights to set. These must be ``float`` s - and correspond to the passed ``uid`` s. - version_key (int): The version key of the validator. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. - 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. + subtensor: Bittensor subtensor object. + wallet: Bittensor wallet object. + netuid: The ``netuid`` of the subnet to set weights for. + uids: The ``uint64`` uids of destination neurons. + weights: The weights to set. These must be ``float``s and correspond to the passed ``uid``s. + version_key: The version key of the validator. + wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns + ``False`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, + or returns ``False`` if the extrinsic fails to be finalized within the timeout. + period: 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: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``True``. + tuple[bool, str]: + `True` if the weight commitment is successful, `False` otherwise. + `msg` is a string value describing the success or potential error. """ # Convert types. uids, weights = convert_uids_and_weights(uids, weights) @@ -217,6 +219,7 @@ def set_weights_extrinsic( use_nonce=True, nonce_key="hotkey", sign_with="hotkey", + raise_error=raise_error, ) if success: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0095b0414d..ef7b3195d3 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -44,7 +44,7 @@ root_set_pending_childkey_cooldown_extrinsic, ) from bittensor.core.extrinsics.commit_reveal import commit_reveal_extrinsic -from bittensor.core.extrinsics.commit_weights import ( +from bittensor.core.extrinsics.weights import ( commit_weights_extrinsic, reveal_weights_extrinsic, ) @@ -75,7 +75,7 @@ get_metadata, serve_axon_extrinsic, ) -from bittensor.core.extrinsics.commit_weights import set_weights_extrinsic +from bittensor.core.extrinsics.weights import set_weights_extrinsic from bittensor.core.extrinsics.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, diff --git a/migration.md b/migration.md index fd60682da9..a08c4cd5e5 100644 --- a/migration.md +++ b/migration.md @@ -153,3 +153,4 @@ It must include: -[x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` -[x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` -[x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` +-[x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py index 6b907a6646..1757cf6e33 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -1,6 +1,6 @@ import pytest -from bittensor.core.extrinsics.commit_weights import ( +from bittensor.core.extrinsics.weights import ( commit_weights_extrinsic, reveal_weights_extrinsic, ) diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py index e41b8f6ba7..5690571ef9 100644 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ b/tests/unit_tests/extrinsics/test_set_weights.py @@ -2,7 +2,7 @@ import pytest -from bittensor.core.extrinsics.commit_weights import ( +from bittensor.core.extrinsics.weights import ( set_weights_extrinsic, ) from bittensor.core.subtensor import Subtensor From 52fab3987dbc3ded10c60e8c151b0520e963d751 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:56:44 -0700 Subject: [PATCH 097/177] temporarily leave the success message `Successfully Set Weights and Finalized.` for `set_weights_extrinsic` bc of test's validator logic works with it --- bittensor/core/extrinsics/asyncex/weights.py | 2 +- bittensor/core/extrinsics/weights.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 3acd1e1ff7..8717bf638d 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -220,7 +220,7 @@ async def set_weights_extrinsic( ) if success: - logging.info(message) + logging.info("Successfully set weights and Finalized.") else: logging.error(f"{get_function_name}: {message}") return success, message diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index ddbf21b5d0..fec8e36b70 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -223,7 +223,7 @@ def set_weights_extrinsic( ) if success: - logging.info(message) + logging.info("Successfully set weights and Finalized.") else: logging.error(f"{get_function_name}: {message}") return success, message From b5ba201783b5ba2fb07d0d883065af5941dcd5f8 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 18:33:51 -0700 Subject: [PATCH 098/177] indent fix --- migration.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/migration.md b/migration.md index a08c4cd5e5..172c20c597 100644 --- a/migration.md +++ b/migration.md @@ -132,25 +132,25 @@ Will greatly simplify tests. To implement the above changes and prepare for the v10 release, the following steps must be taken: --[x] Create a new branch named SDKv10.~~ +- [x] Create a new branch named SDKv10.~~ All breaking changes and refactors should be targeted into this branch to isolate them from staging and maintain backward compatibility during development. --[ ] Add a `migration.md` document at the root of the repository and use it as a check list. This file will serve as a changelog and technical reference. +- [ ] Add a `migration.md` document at the root of the repository and use it as a check list. This file will serve as a changelog and technical reference. It must include: - -[ ] All change categories (Extrinsics, Subtensor, Metagraph, etc.) - -[ ] Per-PR breakdown of what was added, removed, renamed, or refactored. - -[ ] Justifications and migration notes for users (if API behavior changed). + - [ ] All change categories (Extrinsics, Subtensor, Metagraph, etc.) + - [ ] Per-PR breakdown of what was added, removed, renamed, or refactored. + - [ ] Justifications and migration notes for users (if API behavior changed). --[ ] Based on the final `migration.md`, develop migration documentation for the community. --[ ] Once complete, merge SDKv10 into staging and release version 10. +- [ ] Based on the final `migration.md`, develop migration documentation for the community. +- [ ] Once complete, merge SDKv10 into staging and release version 10. # Migration guide --[x] `._do_commit_reveal_v3` logic is included in the main code `.commit_reveal_v3_extrinsic` --[x] `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` --[x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` --[x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` --[x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` --[x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` --[x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` --[x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) +- [x] `._do_commit_reveal_v3` logic is included in the main code `.commit_reveal_v3_extrinsic` +- [x] `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` +- [x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` +- [x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` +- [x] `._do_reveal_weights` logic is included in the main code `.reveal_weights_extrinsic` +- [x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` +- [x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` +- [x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) From 43b7f0171125f6b61b087470405d2e52c37bf5e0 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 19:19:27 -0700 Subject: [PATCH 099/177] `_do_burned_register` logic is included in the main code `.burned_register_extrinsic` --- .../core/extrinsics/asyncex/registration.py | 140 +++++++----------- bittensor/core/extrinsics/registration.py | 131 ++++++---------- migration.md | 1 + .../extrinsics/test_registration.py | 23 +-- 4 files changed, 107 insertions(+), 188 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index d8ac53c499..fbe3becadd 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -1,6 +1,5 @@ """ -This module provides asynchronous functionalities for registering a wallet with the subtensor network using -Proof-of-Work (PoW). +This module provides async functionalities for registering a wallet with the subtensor network using Proof-of-Work (PoW). Extrinsics: - register_extrinsic: Registers the wallet to the subnet. @@ -21,58 +20,6 @@ from bittensor.utils.registration.pow import POWSolution -async def _do_burned_register( - subtensor: "AsyncSubtensor", - netuid: int, - wallet: "Wallet", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - period: Optional[int] = None, -) -> tuple[bool, str]: - """ - Performs a burned register extrinsic call to the Subtensor chain. - - This method sends a registration transaction to the Subtensor blockchain using the burned register mechanism. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Subtensor instance. - netuid (int): The network unique identifier to register on. - wallet (bittensor_wallet.Wallet): The wallet to be registered. - wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is False. - wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is True. - 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. - - Returns: - Tuple[bool, Optional[str]]: A tuple containing a boolean indicating success or failure, and an optional error - message. - """ - - # create extrinsic call - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - fee = await get_extrinsic_fee( - subtensor=subtensor, call=call, keypair=wallet.coldkeypub - ) - logging.info( - f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." - ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) - - async def burned_register_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", @@ -80,20 +27,22 @@ async def burned_register_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, ) -> bool: """Registers the wallet to chain by recycling TAO. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Subtensor instance. - wallet (bittensor.wallet): Bittensor wallet object. - netuid (int): The ``netuid`` of the subnet to register on. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. - 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. + subtensor: Subtensor instance. + wallet: Bittensor wallet object. + netuid: The ``netuid`` of the subnet to register on. + wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns + ``False`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, + or returns ``False`` if the extrinsic fails to be finalized within the timeout. + period: 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: success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for @@ -135,40 +84,57 @@ async def burned_register_extrinsic( logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") - success, err_msg = await _do_burned_register( - subtensor=subtensor, - netuid=netuid, + # create extrinsic call + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + fee = await get_extrinsic_fee( + subtensor=subtensor, call=call, keypair=wallet.coldkeypub + ) + logging.info( + f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not success: - logging.error(f":cross_mark: [red]Failed error:[/red] {err_msg}") + logging.error(f":cross_mark: [red]Failed error:[/red] {message}") await asyncio.sleep(0.5) return False + + # TODO: It is worth deleting everything below and simply returning the result without additional verification. This + # should be the responsibility of the user. We will also reduce the number of calls to the chain. # Successful registration, final check for neuron and pubkey - else: - logging.info(":satellite: [magenta]Checking Balance...[/magenta]") - block_hash = await subtensor.substrate.get_chain_head() - new_balance = await subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=block_hash - ) + logging.info(":satellite: [magenta]Checking Balance...[/magenta]") + block_hash = await subtensor.substrate.get_chain_head() + new_balance = await subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - logging.info(":white_heavy_check_mark: [green]Registered[/green]") - return True - else: - # neuron not found, try again - logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - return False + logging.info( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + logging.info(":white_heavy_check_mark: [green]Registered[/green]") + return True + + # neuron not found, try again + logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + return False async def _do_pow_register( diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 8c090c3065..000a770427 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -1,5 +1,5 @@ """ -This module provides functionalities for registering a wallet with the subtensor network using Proof-of-Work (PoW). +This module provides sync functionalities for registering a wallet with the subtensor network using Proof-of-Work (PoW). Extrinsics: - register_extrinsic: Registers the wallet to the subnet. @@ -20,56 +20,6 @@ from bittensor.utils.registration.pow import POWSolution -def _do_burned_register( - subtensor: "Subtensor", - netuid: int, - wallet: "Wallet", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - period: Optional[int] = None, -) -> tuple[bool, str]: - """ - Performs a burned register extrinsic call to the Subtensor chain. - - This method sends a registration transaction to the Subtensor blockchain using the burned register mechanism. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance. - netuid (int): The network unique identifier to register on. - wallet (bittensor_wallet.Wallet): The wallet to be registered. - wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is False. - wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is True. - 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. - - Returns: - Tuple[bool, Optional[str]]: A tuple containing a boolean indicating success or failure, and an optional error - message. - """ - - # create extrinsic call - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - fee = get_extrinsic_fee(subtensor=subtensor, call=call, keypair=wallet.coldkeypub) - logging.info( - f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." - ) - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) - - def burned_register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -77,20 +27,22 @@ def burned_register_extrinsic( wait_for_inclusion: bool = False, wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, ) -> bool: """Registers the wallet to chain by recycling TAO. Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance. - wallet (bittensor.wallet): Bittensor wallet object. - netuid (int): The ``netuid`` of the subnet to register on. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. - 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. + subtensor: Subtensor instance. + wallet: Bittensor wallet object. + netuid: The ``netuid`` of the subnet to register on. + wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns + ``False`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, + or returns ``False`` if the extrinsic fails to be finalized within the timeout. + period: 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: success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for @@ -128,38 +80,53 @@ def burned_register_extrinsic( logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") logging.info(f"Recycling {recycle_amount} to register on subnet:{netuid}") - success, err_msg = _do_burned_register( - subtensor=subtensor, - netuid=netuid, + # create extrinsic call + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + fee = get_extrinsic_fee(subtensor=subtensor, call=call, keypair=wallet.coldkeypub) + logging.info( + f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not success: - logging.error(f":cross_mark: [red]Failed error:[/red] {err_msg}") + logging.error(f":cross_mark: [red]Failed error:[/red] {message}") time.sleep(0.5) return False + + # TODO: It is worth deleting everything below and simply returning the result without additional verification. This + # should be the responsibility of the user. We will also reduce the number of calls to the chain. # Successful registration, final check for neuron and pubkey - else: - logging.info(":satellite: [magenta]Checking Balance...[/magenta]") - block = subtensor.get_current_block() - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) + logging.info(":satellite: [magenta]Checking Balance...[/magenta]") + block = subtensor.get_current_block() + new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address, block=block - ) - if is_registered: - logging.info(":white_heavy_check_mark: [green]Registered[/green]") - return True - else: - # neuron not found, try again - logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - return False + logging.info( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address, block=block + ) + if is_registered: + logging.info(":white_heavy_check_mark: [green]Registered[/green]") + return True + + # neuron not found, try again + logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") + return False def _do_pow_register( diff --git a/migration.md b/migration.md index 172c20c597..dc07de8cab 100644 --- a/migration.md +++ b/migration.md @@ -154,3 +154,4 @@ It must include: - [x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` - [x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` - [x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) +- [x] `_do_burned_register` logic is included in the main code `.burned_register_extrinsic` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index 45b07661d2..785a313f6e 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -1,20 +1,3 @@ -# The MIT License (MIT) -# Copyright © 2024 Opentensor Foundation -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -# documentation files (the “Software”), to deal in the Software without restriction, including without limitation -# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all copies or substantial portions of -# the Software. -# -# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO -# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -# DEALINGS IN THE SOFTWARE. - import pytest from bittensor_wallet import Wallet @@ -215,8 +198,10 @@ def test_burned_register_extrinsic( "get_neuron_for_pubkey_and_subnet", return_value=mocker.MagicMock(is_null=neuron_is_null), ) - mocker.patch( - "bittensor.core.extrinsics.registration._do_burned_register", + mocker.patch("bittensor.core.extrinsics.registration.get_extrinsic_fee") + mocker.patch.object( + mock_subtensor, + "sign_and_send_extrinsic", return_value=(recycle_success, "Mock error message"), ) mocker.patch.object( From 2ffe83b819904043408f51d4dc39c42672a9e100 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 20:08:30 -0700 Subject: [PATCH 100/177] order --- .../core/extrinsics/asyncex/registration.py | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index fbe3becadd..c6bf71cee6 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -186,6 +186,67 @@ async def _do_pow_register( ) +async def register_subnet_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, + period: Optional[int] = None, +) -> bool: + """ + Registers a new subnetwork on the Bittensor blockchain asynchronously. + + Args: + subtensor (AsyncSubtensor): The async subtensor interface to send the extrinsic. + wallet (Wallet): The wallet to be used for subnet registration. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning true. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning true. + 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. + + Returns: + bool: True if the subnet registration was successful, False otherwise. + """ + balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + burn_cost = await subtensor.get_subnet_burn_cost() + + if burn_cost > balance: + logging.error( + f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO" + ) + return False + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + "mechid": 1, + }, + ) + + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + ) + + if not wait_for_finalization and not wait_for_inclusion: + return True + + if success: + logging.success( + ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" + ) + return True + + logging.error(f"Failed to register subnet: {message}") + return False + + async def register_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", @@ -373,67 +434,6 @@ async def register_extrinsic( return False -async def register_subnet_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - period: Optional[int] = None, -) -> bool: - """ - Registers a new subnetwork on the Bittensor blockchain asynchronously. - - Args: - subtensor (AsyncSubtensor): The async subtensor interface to send the extrinsic. - wallet (Wallet): The wallet to be used for subnet registration. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning true. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning true. - 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. - - Returns: - bool: True if the subnet registration was successful, False otherwise. - """ - balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - burn_cost = await subtensor.get_subnet_burn_cost() - - if burn_cost > balance: - logging.error( - f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO" - ) - return False - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - - success, message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) - - if not wait_for_finalization and not wait_for_inclusion: - return True - - if success: - logging.success( - ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" - ) - return True - - logging.error(f"Failed to register subnet: {message}") - return False - - async def set_subnet_identity_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", From a3a5680ae6dbc1c02d3261641de28954fffb3708 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 10:31:00 -0700 Subject: [PATCH 101/177] `_do_pow_register` logic is included in the main code `.register_extrinsic` + fixed tests --- .../core/extrinsics/asyncex/registration.py | 73 ++----- bittensor/core/extrinsics/registration.py | 78 ++----- migration.md | 3 +- .../extrinsics/asyncex/test_registration.py | 202 +++--------------- .../extrinsics/test_registration.py | 5 +- 5 files changed, 74 insertions(+), 287 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index c6bf71cee6..051c1badf8 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -17,7 +17,6 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet from bittensor.core.async_subtensor import AsyncSubtensor - from bittensor.utils.registration.pow import POWSolution async def burned_register_extrinsic( @@ -137,55 +136,6 @@ async def burned_register_extrinsic( return False -async def _do_pow_register( - subtensor: "AsyncSubtensor", - netuid: int, - wallet: "Wallet", - pow_result: "POWSolution", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - period: Optional[int] = None, -) -> tuple[bool, Optional[str]]: - """Sends a (POW) register extrinsic to the chain. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor to send the extrinsic to. - netuid (int): The subnet to register on. - wallet (bittensor.wallet): The wallet to register. - pow_result (POWSolution): The PoW result to register. - wait_for_inclusion (bool): If ``True``, waits for the extrinsic to be included in a block. Default to `False`. - wait_for_finalization (bool): If ``True``, waits for the extrinsic to be finalized. Default to `True`. - 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. - - Returns: - success (bool): ``True`` if the extrinsic was included in a block. - error (Optional[str]): ``None`` on success or not waiting for inclusion/finalization, otherwise the error - message. - """ - # create extrinsic call - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": netuid, - "block_number": pow_result.block_number, - "nonce": pow_result.nonce, - "work": [int(byte_) for byte_ in pow_result.seal], - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - }, - ) - return await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) - - async def register_subnet_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", @@ -375,28 +325,37 @@ async def register_extrinsic( logging.info(":satellite: [magenta]Submitting POW...[/magenta]") # check if a pow result is still valid while not await pow_result.is_stale_async(subtensor=subtensor): - result: tuple[bool, Optional[str]] = await _do_pow_register( - subtensor=subtensor, - netuid=netuid, + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register", + call_params={ + "netuid": netuid, + "block_number": pow_result.block_number, + "nonce": pow_result.nonce, + "work": [int(byte_) for byte_ in pow_result.seal], + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + }, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - pow_result=pow_result, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, ) - success, err_msg = result if not success: # Look error here # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs - if "HotKeyAlreadyRegisteredInSubNet" in err_msg: + if "HotKeyAlreadyRegisteredInSubNet" in message: logging.info( f":white_heavy_check_mark: [green]Already Registered on subnet:[/green] " f"[blue]{netuid}[/blue]." ) return True - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") + logging.error(f":cross_mark: [red]Failed[/red]: {message}") await asyncio.sleep(0.5) # Successful registration, final check for neuron and pubkey diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 000a770427..730aeee2e6 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -17,7 +17,6 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet from bittensor.core.subtensor import Subtensor - from bittensor.utils.registration.pow import POWSolution def burned_register_extrinsic( @@ -129,56 +128,6 @@ def burned_register_extrinsic( return False -def _do_pow_register( - subtensor: "Subtensor", - netuid: int, - wallet: "Wallet", - pow_result: "POWSolution", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - period: Optional[int] = None, -) -> tuple[bool, Optional[str]]: - """Sends a (POW) register extrinsic to the chain. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor to send the extrinsic to. - netuid (int): The subnet to register on. - wallet (bittensor.wallet): The wallet to register. - pow_result (POWSolution): The PoW result to register. - wait_for_inclusion (bool): If ``True``, waits for the extrinsic to be included in a block. Default to `False`. - wait_for_finalization (bool): If ``True``, waits for the extrinsic to be finalized. Default to `True`. - 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. - - Returns: - success (bool): ``True`` if the extrinsic was included in a block. - error (Optional[str]): ``None`` on success or not waiting for inclusion/finalization, otherwise the error - message. - """ - # create extrinsic call - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": netuid, - "block_number": pow_result.block_number, - "nonce": pow_result.nonce, - "work": [int(byte_) for byte_ in pow_result.seal], - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - }, - ) - logging.debug(":satellite: [magenta]Sending POW Register Extrinsic...[/magenta]") - return subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) - - def register_subnet_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -368,28 +317,41 @@ def register_extrinsic( logging.info(":satellite: [magenta]Submitting POW...[/magenta]") # check if a pow result is still valid while not pow_result.is_stale(subtensor=subtensor): - result: tuple[bool, Optional[str]] = _do_pow_register( - subtensor=subtensor, - netuid=netuid, + # create extrinsic call + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register", + call_params={ + "netuid": netuid, + "block_number": pow_result.block_number, + "nonce": pow_result.nonce, + "work": [int(byte_) for byte_ in pow_result.seal], + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + }, + ) + logging.debug( + ":satellite: [magenta]Sending POW Register Extrinsic...[/magenta]" + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - pow_result=pow_result, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, ) - success, err_msg = result if not success: # Look error here # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs - if "HotKeyAlreadyRegisteredInSubNet" in err_msg: + if "HotKeyAlreadyRegisteredInSubNet" in message: logging.info( f":white_heavy_check_mark: [green]Already Registered on subnet:[/green] " f"[blue]{netuid}[/blue]." ) return True - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") + logging.error(f":cross_mark: [red]Failed[/red]: {message}") time.sleep(0.5) # Successful registration, final check for neuron and pubkey diff --git a/migration.md b/migration.md index dc07de8cab..213dc88665 100644 --- a/migration.md +++ b/migration.md @@ -154,4 +154,5 @@ It must include: - [x] `._do_set_weights` logic is included in the main code `.set_weights_extrinsic` - [x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` - [x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) -- [x] `_do_burned_register` logic is included in the main code `.burned_register_extrinsic` \ No newline at end of file +- [x] `_do_burned_register` logic is included in the main code `.burned_register_extrinsic` +- [x] `_do_pow_register` logic is included in the main code `.register_extrinsic` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index 4cecae6616..181394a8bb 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -3,157 +3,6 @@ from bittensor.core.extrinsics.asyncex import registration as async_registration -@pytest.mark.asyncio -async def test_do_pow_register_success(subtensor, fake_wallet, mocker): - """Tests successful PoW registration.""" - # Preps - fake_wallet.hotkey.ss58_address = "hotkey_ss58" - fake_wallet.coldkeypub.ss58_address = "coldkey_ss58" - fake_pow_result = mocker.Mock( - block_number=12345, - nonce=67890, - seal=b"fake_seal", - ) - - mocker.patch.object(subtensor.substrate, "compose_call") - mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - new=mocker.AsyncMock(return_value=(True, "")), - ) - - # Call - result, error_message = await async_registration._do_pow_register( - subtensor=subtensor, - netuid=1, - wallet=fake_wallet, - pow_result=fake_pow_result, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - subtensor.substrate.compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": 1, - "block_number": 12345, - "nonce": 67890, - "work": list(b"fake_seal"), - "hotkey": "hotkey_ss58", - "coldkey": "coldkey_ss58", - }, - ) - subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - ) - assert result is True - assert error_message == "" - - -@pytest.mark.asyncio -async def test_do_pow_register_failure(subtensor, fake_wallet, mocker): - """Tests failed PoW registration.""" - # Preps - fake_wallet.hotkey.ss58_address = "hotkey_ss58" - fake_wallet.coldkeypub.ss58_address = "coldkey_ss58" - fake_pow_result = mocker.Mock( - block_number=12345, - nonce=67890, - seal=b"fake_seal", - ) - - mocker.patch.object(subtensor.substrate, "compose_call") - mocker.patch.object(subtensor, "sign_and_send_extrinsic") - - # Call - result_error_message = await async_registration._do_pow_register( - subtensor=subtensor, - netuid=1, - wallet=fake_wallet, - pow_result=fake_pow_result, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - subtensor.substrate.compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": 1, - "block_number": 12345, - "nonce": 67890, - "work": list(b"fake_seal"), - "hotkey": "hotkey_ss58", - "coldkey": "coldkey_ss58", - }, - ) - subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - ) - - assert result_error_message == subtensor.sign_and_send_extrinsic.return_value - - -@pytest.mark.asyncio -async def test_do_pow_register_no_waiting(subtensor, fake_wallet, mocker): - """Tests PoW registration without waiting for inclusion or finalization.""" - # Preps - fake_wallet.hotkey.ss58_address = "hotkey_ss58" - fake_wallet.coldkeypub.ss58_address = "coldkey_ss58" - fake_pow_result = mocker.Mock( - block_number=12345, - nonce=67890, - seal=b"fake_seal", - ) - - mocker.patch.object(subtensor.substrate, "compose_call") - mocker.patch.object(subtensor, "sign_and_send_extrinsic") - - # Call - result = await async_registration._do_pow_register( - subtensor=subtensor, - netuid=1, - wallet=fake_wallet, - pow_result=fake_pow_result, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - # Asserts - subtensor.substrate.compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="register", - call_params={ - "netuid": 1, - "block_number": 12345, - "nonce": 67890, - "work": list(b"fake_seal"), - "hotkey": "hotkey_ss58", - "coldkey": "coldkey_ss58", - }, - ) - subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=False, - wait_for_finalization=False, - period=None, - ) - - assert result == subtensor.sign_and_send_extrinsic.return_value - - @pytest.mark.asyncio async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): """Tests successful registration.""" @@ -172,10 +21,13 @@ async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): mocked_create_pow = mocker.patch.object( async_registration, "create_pow_async", - return_value=mocker.Mock(is_stale_async=mocker.AsyncMock(return_value=False)), + return_value=mocker.Mock( + is_stale_async=mocker.AsyncMock(return_value=False), seal=[] + ), ) - mocked_do_pow_register = mocker.patch.object( - async_registration, "_do_pow_register", return_value=(True, None) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, None) ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, "is_hotkey_registered", return_value=True @@ -201,7 +53,13 @@ async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): block_hash=subtensor.substrate.get_chain_head.return_value, ) mocked_create_pow.assert_called_once() - mocked_do_pow_register.assert_called_once() + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" ) @@ -227,10 +85,13 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, mock mocked_create_pow = mocker.patch.object( async_registration, "create_pow_async", - return_value=mocker.Mock(is_stale_async=mocker.AsyncMock(return_value=False)), + return_value=mocker.Mock( + is_stale_async=mocker.AsyncMock(return_value=False), seal=[] + ), ) - mocked_do_pow_register = mocker.patch.object( - async_registration, "_do_pow_register", return_value=(True, None) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, None) ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, "is_hotkey_registered", return_value=True @@ -257,7 +118,13 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, mock block_hash=subtensor.substrate.get_chain_head.return_value, ) mocked_create_pow.assert_called_once() - mocked_do_pow_register.assert_called_once() + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" ) @@ -366,6 +233,7 @@ async def is_stale_side_effect(*_, **__): fake_pow_result = mocker.Mock() fake_pow_result.is_stale_async = mocker.AsyncMock(side_effect=is_stale_side_effect) + fake_pow_result.seal = [] mocked_subnet_exists = mocker.patch.object( subtensor, "subnet_exists", return_value=True @@ -380,10 +248,9 @@ async def is_stale_side_effect(*_, **__): "create_pow_async", return_value=fake_pow_result, ) - mocked_do_pow_register = mocker.patch.object( - async_registration, - "_do_pow_register", - return_value=(False, "Test Error"), + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(False, "Test Error") ) # Call @@ -407,13 +274,10 @@ async def is_stale_side_effect(*_, **__): block_hash=subtensor.substrate.get_chain_head.return_value, ) assert mocked_create_pow.call_count == 3 - assert mocked_do_pow_register.call_count == 3 - - mocked_do_pow_register.assert_called_with( - subtensor=subtensor, - netuid=1, + assert mocked_sign_and_send_extrinsic.call_count == 3 + mocked_sign_and_send_extrinsic.assert_called_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - pow_result=fake_pow_result, wait_for_inclusion=True, wait_for_finalization=True, period=None, diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index 785a313f6e..97b0fb785f 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -129,8 +129,9 @@ def test_register_extrinsic_with_pow( "bittensor.utils.registration.pow._solve_for_difficulty_fast_cuda", return_value=mock_pow_solution if pow_success else None, ) - mocker.patch( - "bittensor.core.extrinsics.registration._do_pow_register", + mocker.patch.object( + mock_subtensor, + "sign_and_send_extrinsic", return_value=(registration_success, "HotKeyAlreadyRegisteredInSubNet"), ) mocker.patch("torch.cuda.is_available", return_value=cuda) From 60fbc2ec0d3f03e12fa10a16e5d2bc2a9cbad001 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 11:36:23 -0700 Subject: [PATCH 102/177] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` + fixed tests --- bittensor/core/extrinsics/asyncex/root.py | 99 ++------- bittensor/core/extrinsics/root.py | 92 ++------- migration.md | 3 +- .../extrinsics/asyncex/test_root.py | 195 ++++-------------- tests/unit_tests/extrinsics/test_root.py | 16 +- 5 files changed, 99 insertions(+), 306 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index 79c5417e4f..3405ebadb8 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -148,74 +148,6 @@ async def root_register_extrinsic( return False -async def _do_set_root_weights( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuids: Union[NDArray[np.int64], list[int]], - weights: Union[NDArray[np.float32], list[float]], - netuid: int = 0, - version_key: int = 0, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = 8, -) -> tuple[bool, str]: - """ - Sets the root weights on the Subnet for the given wallet hotkey account. - - This function constructs and submits an extrinsic to set the root weights for the given wallet hotkey account. - It waits for inclusion or finalization of the extrinsic based on the provided parameters. - - Arguments: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The AsyncSubtensor object used to interact with the - blockchain. - wallet (bittensor_wallet.Wallet): The wallet containing the hotkey and coldkey for the transaction. - netuids (Union[NDArray[np.int64], list[int]]): List of UIDs to set weights for. - weights (Union[NDArray[np.float32], list[float]]): Corresponding weights to set for each UID. - netuid (int): The netuid of the subnet to set weights for. Defaults to 0. - version_key (int, optional): The version key of the validator. Defaults to 0. - wait_for_inclusion (bool, optional): If True, waits for the extrinsic to be included in a block. Defaults to - False. - wait_for_finalization (bool, optional): If True, waits for the extrinsic to be finalized on the chain. Defaults - to False. - 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. - - Returns: - tuple: Returns a tuple containing a boolean indicating success and a message describing the result of the - operation. - """ - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_root_weights", - call_params={ - "dests": netuids, - "weights": weights, - "netuid": netuid, - "version_key": version_key, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - - success, message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True, message - - if success: - return True, "Successfully set weights." - - return False, message - - async def set_root_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", @@ -286,24 +218,35 @@ async def set_root_weights_extrinsic( logging.info(":satellite: [magenta]Setting root weights...[magenta]") weight_uids, weight_vals = convert_weights_and_uids_for_emit(netuids, weights) - success, error_message = await _do_set_root_weights( - subtensor=subtensor, + # Since this extrinsic is only for the root network, we can set netuid to 0. + netuid = 0 + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_root_weights", + call_params={ + "dests": weight_uids, + "weights": weight_vals, + "netuid": netuid, + "version_key": version_key, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + + success, message = await subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - netuids=weight_uids, - weights=weight_vals, - version_key=version_key, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + use_nonce=True, period=period, ) - if success is True: + if success: logging.info(":white_heavy_check_mark: [green]Finalized[/green]") return True - else: - fmt_err = error_message - logging.error(f":cross_mark: [red]Failed error:[/red] {fmt_err}") - return False + + logging.error(f":cross_mark: [red]Failed error:[/red] {message}") + return False except SubstrateRequestException as e: fmt_err = format_error_message(e) diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index f59a1104d7..66ee850194 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -149,74 +149,6 @@ def root_register_extrinsic( return False -def _do_set_root_weights( - subtensor: "Subtensor", - wallet: "Wallet", - netuids: Union[NDArray[np.int64], list[int]], - weights: Union[NDArray[np.float32], list[float]], - netuid: int = 0, - version_key: int = 0, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = 8, -) -> tuple[bool, str]: - """ - Sets the root weights on the Subnet for the given wallet hotkey account. - - This function constructs and submits an extrinsic to set the root weights for the given wallet hotkey account. - It waits for inclusion or finalization of the extrinsic based on the provided parameters. - - Arguments: - subtensor (bittensor.core.subtensor.Subtensor): The Subtensor object used to interact with the - blockchain. - wallet (bittensor_wallet.Wallet): The wallet containing the hotkey and coldkey for the transaction. - netuids (Union[NDArray[np.int64], list[int]]): List of UIDs to set weights for. - weights (Union[NDArray[np.float32], list[float]]): Corresponding weights to set for each UID. - netuid (int): The netuid of the subnet to set weights for. Defaults to 0. - version_key (int, optional): The version key of the validator. Defaults to 0. - wait_for_inclusion (bool, optional): If True, waits for the extrinsic to be included in a block. Defaults to - False. - wait_for_finalization (bool, optional): If True, waits for the extrinsic to be finalized on the chain. Defaults - to False. - 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. - - Returns: - tuple: Returns a tuple containing a boolean indicating success and a message describing the result of the - operation. - """ - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_root_weights", - call_params={ - "dests": netuids, - "weights": weights, - "netuid": netuid, - "version_key": version_key, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True, "Not waiting for finalization or inclusion." - - if success: - return True, "Successfully set weights." - - return False, message - - def set_root_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -287,18 +219,30 @@ def set_root_weights_extrinsic( logging.info(":satellite: [magenta]Setting root weights...[magenta]") weight_uids, weight_vals = convert_weights_and_uids_for_emit(netuids, weights) - success, message = _do_set_root_weights( - subtensor=subtensor, + # Since this extrinsic is only for the root network, we can set netuid to 0. + netuid = 0 + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_root_weights", + call_params={ + "dests": weight_uids, + "weights": weight_vals, + "netuid": netuid, + "version_key": version_key, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + + success, message = subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - netuids=weight_uids, - weights=weight_vals, - version_key=version_key, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + use_nonce=True, period=period, ) - if success is True: + if success: logging.info(":white_heavy_check_mark: [green]Finalized[/green]") return True diff --git a/migration.md b/migration.md index 213dc88665..e5c745dc26 100644 --- a/migration.md +++ b/migration.md @@ -155,4 +155,5 @@ It must include: - [x] `set_weights_extrinsic` moved to `bittensor/core/extrinsics/commit_weights.py` - [x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) - [x] `_do_burned_register` logic is included in the main code `.burned_register_extrinsic` -- [x] `_do_pow_register` logic is included in the main code `.register_extrinsic` \ No newline at end of file +- [x] `_do_pow_register` logic is included in the main code `.register_extrinsic` +- [x] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_root.py b/tests/unit_tests/extrinsics/asyncex/test_root.py index baa134ee92..b4c32a1e87 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_root.py +++ b/tests/unit_tests/extrinsics/asyncex/test_root.py @@ -323,134 +323,6 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc assert result is False -@pytest.mark.asyncio -async def test_do_set_root_weights_success(subtensor, fake_wallet, mocker): - """Tests _do_set_root_weights when weights are set successfully.""" - # Preps - fake_wallet.hotkey.ss58_address = "fake_hotkey_address" - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] - - fake_call = mocker.AsyncMock() - fake_extrinsic = True, "Successfully set weights." - fake_response = mocker.Mock() - - fake_response.is_success = mocker.AsyncMock(return_value=True)() - fake_response.process_events = mocker.AsyncMock() - - mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) - mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=fake_extrinsic - ) - - # Call - result, message = await async_root._do_set_root_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuids=fake_uids, - weights=fake_weights, - version_key=0, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="set_root_weights", - call_params={ - "dests": fake_uids, - "weights": fake_weights, - "netuid": 0, - "version_key": 0, - "hotkey": "fake_hotkey_address", - }, - ) - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=fake_call, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - use_nonce=True, - period=8, - ) - assert result is True - assert message == "Successfully set weights." - - -@pytest.mark.asyncio -async def test_do_set_root_weights_failure(subtensor, fake_wallet, mocker): - """Tests _do_set_root_weights when setting weights fails.""" - # Preps - fake_wallet.hotkey.ss58_address = "fake_hotkey_address" - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] - - fake_call = mocker.AsyncMock() - - mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) - mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(False, "Transaction failed") - ) - - # Call - result, message = await async_root._do_set_root_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuids=fake_uids, - weights=fake_weights, - version_key=0, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert result is False - - -@pytest.mark.asyncio -async def test_do_set_root_weights_no_waiting(subtensor, fake_wallet, mocker): - """Tests _do_set_root_weights when not waiting for inclusion or finalization.""" - # Preps - fake_wallet.hotkey.ss58_address = "fake_hotkey_address" - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] - - fake_call = mocker.AsyncMock() - fake_extrinsic = mocker.AsyncMock() - - mocker.patch.object(subtensor.substrate, "compose_call", return_value=fake_call) - mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - return_value=(True, "Not waiting for finalization or inclusion."), - ) - - # Call - result, message = await async_root._do_set_root_weights( - subtensor=subtensor, - wallet=fake_wallet, - netuids=fake_uids, - weights=fake_weights, - version_key=0, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once() - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=False, - wait_for_finalization=False, - use_nonce=True, - period=8, - ) - assert result is True - assert message == "Not waiting for finalization or inclusion." - - @pytest.mark.asyncio async def test_set_root_weights_extrinsic_success(subtensor, fake_wallet, mocker): """Tests successful setting of root weights.""" @@ -464,9 +336,10 @@ async def test_set_root_weights_extrinsic_success(subtensor, fake_wallet, mocker ) mocker.patch.object(async_root, "_get_limits", return_value=(2, 1.0)) mocker.patch.object(async_root, "normalize_max_weight", return_value=weights) - mocked_do_set_root_weights = mocker.patch.object( - async_root, - "_do_set_root_weights", + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", return_value=(True, ""), ) @@ -479,7 +352,14 @@ async def test_set_root_weights_extrinsic_success(subtensor, fake_wallet, mocker wait_for_finalization=True, ) - mocked_do_set_root_weights.assert_called_once() + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + use_nonce=True, + ) assert result is True @@ -496,9 +376,10 @@ async def test_set_root_weights_extrinsic_no_waiting(subtensor, fake_wallet, moc ) mocker.patch.object(async_root, "_get_limits", return_value=(2, 1.0)) mocker.patch.object(async_root, "normalize_max_weight", return_value=weights) - mocked_do_set_root_weights = mocker.patch.object( - async_root, - "_do_set_root_weights", + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", return_value=(True, ""), ) @@ -511,7 +392,14 @@ async def test_set_root_weights_extrinsic_no_waiting(subtensor, fake_wallet, moc wait_for_finalization=False, ) - mocked_do_set_root_weights.assert_called_once() + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=False, + wait_for_finalization=False, + period=None, + use_nonce=True, + ) assert result is True @@ -595,10 +483,11 @@ async def test_set_root_weights_extrinsic_transaction_failed( mocker.patch.object( async_root, "normalize_max_weight", return_value=[0.1, 0.2, 0.7] ) - mocked_do_set_root_weights = mocker.patch.object( - async_root, - "_do_set_root_weights", - return_value=(False, "Transaction failed"), + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", + return_value=(True, ""), ) result = await async_root.set_root_weights_extrinsic( @@ -610,8 +499,15 @@ async def test_set_root_weights_extrinsic_transaction_failed( wait_for_finalization=True, ) - mocked_do_set_root_weights.assert_called_once() - assert result is False + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + use_nonce=True, + ) + assert result is True @pytest.mark.asyncio @@ -626,9 +522,10 @@ async def test_set_root_weights_extrinsic_request_exception( async_root, "unlock_key", return_value=mocker.Mock(success=True) ) mocker.patch.object(async_root, "_get_limits", return_value=(2, 1.0)) - mocked_do_set_root_weights = mocker.patch.object( - async_root, - "_do_set_root_weights", + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, + "sign_and_send_extrinsic", side_effect=SubstrateRequestException("Request failed"), ) mocked_format_error_message = mocker.patch.object( @@ -645,14 +542,12 @@ async def test_set_root_weights_extrinsic_request_exception( ) assert result is False - mocked_do_set_root_weights.assert_called_once_with( - subtensor=subtensor, + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, wallet=fake_wallet, - netuids=[1, 2, 3], - weights=[9362, 18724, 65535], - version_key=0, wait_for_inclusion=True, wait_for_finalization=True, period=None, + use_nonce=True, ) mocked_format_error_message.assert_called_once() diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index 8737b3f32c..e3ef90cf0e 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -184,8 +184,11 @@ def test_set_root_weights_extrinsic( mocker, ): # Preps - mocker.patch.object( - root, "_do_set_root_weights", return_value=(expected_success, "Mock error") + mocked_compose_call = mocker.patch.object(mock_subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + mock_subtensor, + "sign_and_send_extrinsic", + return_value=(expected_success, "Mock error"), ) mocker.patch.object( root, @@ -203,7 +206,14 @@ def test_set_root_weights_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=mock_wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=None, + use_nonce=True, + ) # Asserts assert result == expected_success From 06e1ad60fec0a1423bc68e631815ff366ae53552 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 12:05:51 -0700 Subject: [PATCH 103/177] `._do_transfer` logic is included in the main code `.transfer_extrinsic` + `dest` parameter has been renamed to `destination` in `transfer_extrinsic` function and `subtensor.transfer` method. + fixed tests --- bittensor/core/async_subtensor.py | 6 +- bittensor/core/extrinsics/asyncex/transfer.py | 85 ++------ bittensor/core/extrinsics/transfer.py | 89 ++------ bittensor/core/subtensor.py | 6 +- migration.md | 4 +- .../extrinsics/asyncex/test_transfer.py | 206 +++--------------- tests/unit_tests/extrinsics/test_transfer.py | 143 ------------ tests/unit_tests/test_async_subtensor.py | 4 +- tests/unit_tests/test_subtensor.py | 12 +- 9 files changed, 84 insertions(+), 471 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 118f60706f..2a8e7bb286 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5506,7 +5506,7 @@ async def toggle_user_liquidity( async def transfer( self, wallet: "Wallet", - dest: str, + destination: str, amount: Optional[Balance], transfer_all: bool = False, wait_for_inclusion: bool = True, @@ -5519,7 +5519,7 @@ async def transfer( Arguments: wallet: Source wallet for the transfer. - dest: Destination address for the transfer. + destination: Destination address for the transfer. amount: Number of tokens to transfer. `None` is transferring all. transfer_all: Flag to transfer all tokens. Default is `False`. wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `True`. @@ -5536,7 +5536,7 @@ async def transfer( return await transfer_extrinsic( subtensor=self, wallet=wallet, - dest=dest, + destination=destination, amount=amount, transfer_all=transfer_all, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index 6b0332cd73..365441d51c 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -16,68 +16,10 @@ from bittensor_wallet import Wallet -async def _do_transfer( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - destination: str, - amount: Optional[Balance], - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, - keep_alive: bool = True, -) -> tuple[bool, str, str]: - """ - Makes transfer from wallet to destination public key address. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): initialized AsyncSubtensor object used for transfer - wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from. - destination (str): Destination public key address (ss58_address or ed25519) of recipient. - amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - 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. - keep_alive (bool): If `True`, will keep the existential deposit in the account. - - Returns: - success, block hash, formatted error message - """ - call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive) - - call = await subtensor.substrate.compose_call( - call_module="Balances", - call_function=call_function, - call_params=call_params, - ) - - success, message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True, "", message - - # Otherwise continue with finalization. - if success: - block_hash_ = await subtensor.get_block_hash() - return True, block_hash_, "Success with response." - - return False, "", message - - async def transfer_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - dest: str, + destination: str, amount: Optional[Balance], transfer_all: bool = False, wait_for_inclusion: bool = True, @@ -90,7 +32,7 @@ async def transfer_extrinsic( Args: subtensor (bittensor.core.async_subtensor.AsyncSubtensor): initialized AsyncSubtensor object used for transfer wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from. - dest (str): Destination public key address (ss58_address or ed25519) of recipient. + destination (str): Destination public key address (ss58_address or ed25519) of recipient. amount (Optional[bittensor.utils.balance.Balance]): Amount to stake as Bittensor balance. `None` if transferring all. transfer_all (bool): Whether to transfer all funds from this wallet to the destination address. @@ -107,8 +49,6 @@ async def transfer_extrinsic( success (bool): Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is `True`, regardless of its inclusion. """ - destination = dest - if amount is None and not transfer_all: logging.error("If not transferring all, `amount` must be specified.") return False @@ -159,18 +99,25 @@ async def transfer_extrinsic( return False logging.info(":satellite: [magenta]Transferring... tuple[bool, str, str]: - """ - Makes transfer from wallet to destination public key address. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): the Subtensor object used for transfer - wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from. - destination (str): Destination public key address (ss58_address or ed25519) of recipient. - amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. `None` if transferring all. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - 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. - keep_alive (bool): If `True`, will keep the existential deposit in the account. - - Returns: - success, block hash, formatted error message - """ - call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive) - - call = subtensor.substrate.compose_call( - call_module="Balances", - call_function=call_function, - call_params=call_params, - ) - - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - ) - - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True, "", message - - # Otherwise continue with finalization. - if success: - block_hash_ = subtensor.get_block_hash() - return True, block_hash_, "Success with response." - - return False, "", message - - def transfer_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - dest: str, + destination: str, amount: Optional[Balance], transfer_all: bool = False, wait_for_inclusion: bool = True, @@ -89,7 +31,7 @@ def transfer_extrinsic( Args: subtensor (bittensor.core.subtensor.Subtensor): the Subtensor object used for transfer wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from. - dest (str): Destination public key address (ss58_address or ed25519) of recipient. + destination (str): Destination public key address (ss58_address or ed25519) of recipient. amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. `None` if transferring all. transfer_all (bool): Whether to transfer all funds from this wallet to the destination address. wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns @@ -110,9 +52,9 @@ def transfer_extrinsic( return False # Validate destination address. - if not is_valid_bittensor_address_or_public_key(dest): + if not is_valid_bittensor_address_or_public_key(destination): logging.error( - f":cross_mark: [red]Invalid destination SS58 address[/red]: {dest}" + f":cross_mark: [red]Invalid destination SS58 address[/red]: {destination}" ) return False @@ -137,7 +79,7 @@ def transfer_extrinsic( existential_deposit = subtensor.get_existential_deposit(block=block) fee = subtensor.get_transfer_fee( - wallet=wallet, dest=dest, value=amount, keep_alive=keep_alive + wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive ) # Check if we have enough balance. @@ -153,18 +95,25 @@ def transfer_extrinsic( return False logging.info(":satellite: [magenta]Transferring...[/magenta]") - success, block_hash, err_msg = _do_transfer( - subtensor=subtensor, + + call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive) + + call = subtensor.substrate.compose_call( + call_module="Balances", + call_function=call_function, + call_params=call_params, + ) + + success, message = subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - destination=dest, - amount=amount, - keep_alive=keep_alive, - wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, period=period, ) if success: + block_hash = subtensor.get_block_hash() logging.success(":white_heavy_check_mark: [green]Finalized[/green]") logging.info(f"[green]Block Hash:[/green] [blue]{block_hash}[/blue]") @@ -188,5 +137,5 @@ def transfer_extrinsic( ) return True - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") + logging.error(f":cross_mark: [red]Failed[/red]: {message}") return False diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ef7b3195d3..1a551ea490 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4312,7 +4312,7 @@ def toggle_user_liquidity( def transfer( self, wallet: "Wallet", - dest: str, + destination: str, amount: Optional[Balance], wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -4325,7 +4325,7 @@ def transfer( Arguments: wallet (bittensor_wallet.Wallet): Source wallet for the transfer. - dest (str): Destination address for the transfer. + destination (str): Destination address for the transfer. amount (float): Amount of tao to transfer. transfer_all (bool): Flag to transfer all tokens. Default is ``False``. wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``True``. @@ -4344,7 +4344,7 @@ def transfer( return transfer_extrinsic( subtensor=self, wallet=wallet, - dest=dest, + destination=destination, amount=amount, transfer_all=transfer_all, wait_for_inclusion=wait_for_inclusion, diff --git a/migration.md b/migration.md index e5c745dc26..497ab042a9 100644 --- a/migration.md +++ b/migration.md @@ -156,4 +156,6 @@ It must include: - [x] `bittensor/core/extrinsics/commit_weights.py` module renamed to `bittensor/core/extrinsics/weights.py` (consistent naming with async module) - [x] `_do_burned_register` logic is included in the main code `.burned_register_extrinsic` - [x] `_do_pow_register` logic is included in the main code `.register_extrinsic` -- [x] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` \ No newline at end of file +- [x] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` +- [x] `._do_transfer` logic is included in the main code `.transfer_extrinsic` +- [x] `dest` parameter has been renamed to `destination` in `transfer_extrinsic` function and `subtensor.transfer` method. \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_transfer.py b/tests/unit_tests/extrinsics/asyncex/test_transfer.py index 299c6df446..a755c8c894 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_transfer.py +++ b/tests/unit_tests/extrinsics/asyncex/test_transfer.py @@ -3,164 +3,6 @@ from bittensor.utils.balance import Balance -@pytest.mark.parametrize( - "amount,keep_alive,call_function,call_params", - [ - ( - Balance(1), - True, - "transfer_keep_alive", - {"dest": "SS58PUBLICKEY", "value": Balance(1).rao}, - ), - (None, True, "transfer_all", {"dest": "SS58PUBLICKEY", "keep_alive": True}), - (None, False, "transfer_all", {"dest": "SS58PUBLICKEY", "keep_alive": False}), - ( - Balance(1), - False, - "transfer_allow_death", - {"dest": "SS58PUBLICKEY", "value": Balance(1).rao}, - ), - ], -) -@pytest.mark.asyncio -async def test_do_transfer_success( - subtensor, fake_wallet, mocker, amount, keep_alive, call_function, call_params -): - """Tests _do_transfer when the transfer is successful.""" - # Preps - fake_destination = "SS58PUBLICKEY" - fake_block_hash = "fake_block_hash" - - mocker.patch.object(subtensor.substrate, "compose_call") - mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - new=mocker.AsyncMock(return_value=(True, "")), - ) - mocker.patch.object(subtensor, "get_block_hash", return_value=fake_block_hash) - - # Call - success, block_hash, error_message = await async_transfer._do_transfer( - subtensor=subtensor, - wallet=fake_wallet, - destination=fake_destination, - amount=amount, - keep_alive=keep_alive, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - subtensor.substrate.compose_call.assert_awaited_once_with( - call_module="Balances", - call_function=call_function, - call_params=call_params, - ) - - subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - ) - assert success is True - assert block_hash == "fake_block_hash" - assert error_message == "Success with response." - - -@pytest.mark.asyncio -async def test_do_transfer_failure(subtensor, fake_wallet, mocker): - """Tests _do_transfer when the transfer fails.""" - # Preps - fake_destination = "destination_address" - fake_amount = mocker.Mock(autospec=Balance, rao=1000) - fake_block_hash = "fake_block_hash" - - mocker.patch.object(subtensor.substrate, "compose_call") - mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - new=mocker.AsyncMock(return_value=(False, "Formatted error message")), - ) - mocker.patch.object(subtensor, "get_block_hash", return_value=fake_block_hash) - - # Call - success, block_hash, error_message = await async_transfer._do_transfer( - subtensor=subtensor, - wallet=fake_wallet, - destination=fake_destination, - amount=fake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - subtensor.substrate.compose_call.assert_awaited_once_with( - call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": fake_destination, "value": fake_amount.rao}, - ) - - subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - ) - assert success is False - assert block_hash == "" - assert error_message == "Formatted error message" - - -@pytest.mark.asyncio -async def test_do_transfer_no_waiting(subtensor, fake_wallet, mocker): - """Tests _do_transfer when no waiting for inclusion or finalization.""" - # Preps - fake_destination = "destination_address" - fake_amount = mocker.Mock(autospec=Balance, rao=1000) - fake_block_hash = "fake_block_hash" - - mocker.patch.object(subtensor.substrate, "compose_call") - mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - new=mocker.AsyncMock( - return_value=(False, "Success, extrinsic submitted without waiting.") - ), - ) - mocker.patch.object(subtensor, "get_block_hash", return_value=fake_block_hash) - - # Call - success, block_hash, error_message = await async_transfer._do_transfer( - subtensor=subtensor, - wallet=fake_wallet, - destination=fake_destination, - amount=fake_amount, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - # Asserts - subtensor.substrate.compose_call.assert_awaited_once_with( - call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": fake_destination, "value": fake_amount.rao}, - ) - - subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=False, - wait_for_finalization=False, - period=None, - ) - assert success is True - assert block_hash == "" - assert error_message == "Success, extrinsic submitted without waiting." - - @pytest.mark.asyncio async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): """Tests successful transfer.""" @@ -193,15 +35,16 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): subtensor.get_transfer_fee = mocker.patch.object( subtensor, "get_transfer_fee", return_value=2 ) - mocked_do_transfer = mocker.patch.object( - async_transfer, "_do_transfer", return_value=(True, "fake_block_hash", "") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") ) # Call result = await async_transfer.transfer_extrinsic( subtensor=subtensor, wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=False, wait_for_inclusion=True, @@ -212,14 +55,20 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) mocked_unlock_key.assert_called_once_with(fake_wallet) - mocked_get_chain_head.assert_called_once() + assert mocked_get_chain_head.call_count == 2 mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, ) mocked_get_existential_deposit.assert_called_once_with( block_hash=mocked_get_chain_head.return_value ) - mocked_do_transfer.assert_called_once() + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + ) assert result is True @@ -257,15 +106,16 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( subtensor.get_transfer_fee = mocker.patch.object( subtensor, "get_transfer_fee", return_value=2 ) - mocked_do_transfer = mocker.patch.object( - async_transfer, "_do_transfer", return_value=(False, "", "") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(False, "") ) # Call result = await async_transfer.transfer_extrinsic( subtensor=subtensor, wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=False, wait_for_inclusion=True, @@ -284,7 +134,13 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( mocked_get_existential_deposit.assert_called_once_with( block_hash=mocked_get_chain_head.return_value ) - mocked_do_transfer.assert_called_once() + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + ) assert result is False @@ -325,7 +181,7 @@ async def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, m result = await async_transfer.transfer_extrinsic( subtensor=subtensor, wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=False, wait_for_inclusion=True, @@ -362,7 +218,7 @@ async def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mo result = await async_transfer.transfer_extrinsic( subtensor=subtensor, wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=False, wait_for_inclusion=True, @@ -399,7 +255,7 @@ async def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocke result = await async_transfer.transfer_extrinsic( subtensor=subtensor, wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=False, wait_for_inclusion=True, @@ -447,15 +303,16 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( subtensor.get_transfer_fee = mocker.patch.object( subtensor, "get_transfer_fee", return_value=2 ) - mocked_do_transfer = mocker.patch.object( - async_transfer, "_do_transfer", return_value=(True, "fake_block_hash", "") + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") ) # Call result = await async_transfer.transfer_extrinsic( subtensor=subtensor, wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=True, wait_for_inclusion=True, @@ -471,5 +328,6 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( mocked_get_existential_deposit.assert_called_once_with( block_hash=mocked_get_chain_head.return_value ) - mocked_do_transfer.assert_not_called() + assert mocked_compose_call.call_count == 0 + assert mocked_sign_and_send_extrinsic.call_count == 0 assert result is False diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index 6aedaea601..e69de29bb2 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -1,143 +0,0 @@ -from bittensor.core.extrinsics.transfer import _do_transfer -from bittensor.utils.balance import Balance - -import pytest - - -@pytest.mark.parametrize( - "amount,keep_alive,call_function,call_params", - [ - ( - Balance(1), - True, - "transfer_keep_alive", - {"dest": "SS58PUBLICKEY", "value": Balance(1).rao}, - ), - (None, True, "transfer_all", {"dest": "SS58PUBLICKEY", "keep_alive": True}), - (None, False, "transfer_all", {"dest": "SS58PUBLICKEY", "keep_alive": False}), - ( - Balance(1), - False, - "transfer_allow_death", - {"dest": "SS58PUBLICKEY", "value": Balance(1).rao}, - ), - ], -) -def test_do_transfer_is_success_true( - subtensor, fake_wallet, mocker, amount, keep_alive, call_function, call_params -): - """Successful do_transfer call.""" - # Prep - fake_dest = "SS58PUBLICKEY" - fake_wait_for_inclusion = True - fake_wait_for_finalization = True - - mocker.patch.object(subtensor, "sign_and_send_extrinsic", return_value=(True, "")) - mocker.patch.object(subtensor, "get_block_hash", return_value=1) - - # Call - result = _do_transfer( - subtensor, - fake_wallet, - fake_dest, - amount, - fake_wait_for_inclusion, - fake_wait_for_finalization, - keep_alive=keep_alive, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once_with( - call_module="Balances", - call_function=call_function, - call_params=call_params, - ) - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - period=None, - ) - # subtensor.substrate.submit_extrinsic.return_value.process_events.assert_called_once() - assert result == (True, 1, "Success with response.") - - -def test_do_transfer_is_success_false(subtensor, fake_wallet, mocker): - """Successful do_transfer call.""" - # Prep - fake_dest = "SS58PUBLICKEY" - fake_transfer_balance = Balance(1) - keep_alive = True - fake_wait_for_inclusion = True - fake_wait_for_finalization = True - - mocker.patch.object(subtensor, "sign_and_send_extrinsic", return_value=(False, "")) - mocker.patch.object(subtensor, "get_block_hash", return_value=1) - - # Call - result = _do_transfer( - subtensor, - fake_wallet, - fake_dest, - fake_transfer_balance, - fake_wait_for_inclusion, - fake_wait_for_finalization, - keep_alive=keep_alive, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once_with( - call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": fake_dest, "value": fake_transfer_balance.rao}, - ) - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - period=None, - ) - - assert result == (False, "", "") - - -def test_do_transfer_no_waits(subtensor, fake_wallet, mocker): - """Successful do_transfer call.""" - # Prep - fake_dest = "SS58PUBLICKEY" - fake_transfer_balance = Balance(1) - keep_alive = True - fake_wait_for_inclusion = False - fake_wait_for_finalization = False - - mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "msg") - ) - - # Call - result = _do_transfer( - subtensor, - fake_wallet, - fake_dest, - fake_transfer_balance, - fake_wait_for_inclusion, - fake_wait_for_finalization, - keep_alive=keep_alive, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once_with( - call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": fake_dest, "value": fake_transfer_balance.rao}, - ) - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - period=None, - ) - assert result == (True, "", "msg") diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 66c0b5dc9b..61ea4b0b0c 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2588,7 +2588,7 @@ async def test_transfer_success(subtensor, fake_wallet, mocker): # Call result = await subtensor.transfer( wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=fake_transfer_all, ) @@ -2597,7 +2597,7 @@ async def test_transfer_success(subtensor, fake_wallet, mocker): mocked_transfer_extrinsic.assert_awaited_once_with( subtensor=subtensor, wallet=fake_wallet, - dest=fake_destination, + destination=fake_destination, amount=fake_amount, transfer_all=fake_transfer_all, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 2980cec909..76a211b782 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1309,18 +1309,18 @@ def test_transfer(subtensor, fake_wallet, mocker): # Call result = subtensor.transfer( - fake_wallet, - fake_dest, - fake_amount, - fake_wait_for_inclusion, - fake_wait_for_finalization, + wallet=fake_wallet, + destination=fake_dest, + amount=fake_amount, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, ) # Asserts mocked_transfer_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, - dest=fake_dest, + destination=fake_dest, amount=Balance(fake_amount), transfer_all=False, wait_for_inclusion=fake_wait_for_inclusion, From 883bb37b8456654b9f816a1a7e9216ea6087efdb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 12:19:22 -0700 Subject: [PATCH 104/177] tests for `bittensor/core/extrinsics/transfer.py` --- tests/unit_tests/extrinsics/test_transfer.py | 320 +++++++++++++++++++ 1 file changed, 320 insertions(+) diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index e69de29bb2..73a696ca9b 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -0,0 +1,320 @@ +import pytest +from bittensor.core.extrinsics import transfer +from bittensor.utils.balance import Balance + + +def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): + """Tests successful transfer.""" + # Preps + fake_wallet.coldkeypub.ss58_address = "fake_ss58_address" + fake_destination = "valid_ss58_address" + fake_amount = Balance(15) + + mocked_is_valid_address = mocker.patch.object( + transfer, + "is_valid_bittensor_address_or_public_key", + return_value=True, + ) + mocked_unlock_key = mocker.patch.object( + transfer, + "unlock_key", + return_value=mocker.Mock(success=True, message="Unlocked"), + ) + mocked_get_chain_head = mocker.patch.object( + subtensor.substrate, "get_chain_head", return_value="some_block_hash" + ) + mocked_get_balance = mocker.patch.object( + subtensor, + "get_balance", + return_value=10000, + ) + mocked_get_existential_deposit = mocker.patch.object( + subtensor, "get_existential_deposit", return_value=1 + ) + subtensor.get_transfer_fee = mocker.patch.object( + subtensor, "get_transfer_fee", return_value=2 + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + result = transfer.transfer_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + destination=fake_destination, + amount=fake_amount, + transfer_all=False, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=True, + ) + + # Asserts + mocked_is_valid_address.assert_called_once_with(fake_destination) + mocked_unlock_key.assert_called_once_with(fake_wallet) + assert mocked_get_chain_head.call_count == 1 + mocked_get_balance.assert_called_with( + fake_wallet.coldkeypub.ss58_address, + ) + mocked_get_existential_deposit.assert_called_once_with( + block=subtensor.substrate.get_block_number.return_value + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + ) + assert result is True + + +def test_transfer_extrinsic_call_successful_with_failed_response( + subtensor, fake_wallet, mocker +): + """Tests successful transfer call is successful with failed response.""" + # Preps + fake_wallet.coldkeypub.ss58_address = "fake_ss58_address" + fake_destination = "valid_ss58_address" + fake_amount = Balance(15) + + mocked_is_valid_address = mocker.patch.object( + transfer, + "is_valid_bittensor_address_or_public_key", + return_value=True, + ) + mocked_unlock_key = mocker.patch.object( + transfer, + "unlock_key", + return_value=mocker.Mock(success=True, message="Unlocked"), + ) + mocked_get_chain_head = mocker.patch.object( + subtensor.substrate, "get_chain_head", return_value="some_block_hash" + ) + mocked_get_balance = mocker.patch.object( + subtensor, + "get_balance", + return_value=10000, + ) + mocked_get_existential_deposit = mocker.patch.object( + subtensor, "get_existential_deposit", return_value=1 + ) + subtensor.get_transfer_fee = mocker.patch.object( + subtensor, "get_transfer_fee", return_value=2 + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(False, "") + ) + + # Call + result = transfer.transfer_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + destination=fake_destination, + amount=fake_amount, + transfer_all=False, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=True, + ) + + # Asserts + mocked_is_valid_address.assert_called_once_with(fake_destination) + mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_get_balance.assert_called_with( + fake_wallet.coldkeypub.ss58_address, + block=subtensor.substrate.get_block_number.return_value + ) + mocked_get_existential_deposit.assert_called_once_with( + block=subtensor.substrate.get_block_number.return_value + ) + mocked_sign_and_send_extrinsic.assert_called_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + period=None, + ) + assert result is False + + +def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, mocker): + """Tests transfer when balance is insufficient.""" + # Preps + fake_wallet.coldkeypub.ss58_address = "fake_ss58_address" + fake_destination = "valid_ss58_address" + fake_amount = Balance(5000) + + mocked_is_valid_address = mocker.patch.object( + transfer, + "is_valid_bittensor_address_or_public_key", + return_value=True, + ) + mocked_unlock_key = mocker.patch.object( + transfer, + "unlock_key", + return_value=mocker.Mock(success=True, message="Unlocked"), + ) + mocked_get_chain_head = mocker.patch.object( + subtensor.substrate, "get_chain_head", return_value="some_block_hash" + ) + mocked_get_balance = mocker.patch.object( + subtensor, + "get_balance", + return_value=1000, # Insufficient balance + ) + mocked_get_existential_deposit = mocker.patch.object( + subtensor, "get_existential_deposit", return_value=1 + ) + subtensor.get_transfer_fee = mocker.patch.object( + subtensor, "get_transfer_fee", return_value=2 + ) + + # Call + result = transfer.transfer_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + destination=fake_destination, + amount=fake_amount, + transfer_all=False, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=True, + ) + + # Asserts + mocked_is_valid_address.assert_called_once_with(fake_destination) + mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_get_balance.assert_called_once() + mocked_get_existential_deposit.assert_called_once_with( + block=subtensor.substrate.get_block_number.return_value + ) + assert result is False + + +def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): + """Tests transfer with invalid destination address.""" + # Preps + fake_wallet.coldkeypub.ss58_address = "fake_ss58_address" + fake_destination = "invalid_address" + fake_amount = Balance(15) + + mocked_is_valid_address = mocker.patch.object( + transfer, + "is_valid_bittensor_address_or_public_key", + return_value=False, + ) + + # Call + result = transfer.transfer_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + destination=fake_destination, + amount=fake_amount, + transfer_all=False, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=True, + ) + + # Asserts + mocked_is_valid_address.assert_called_once_with(fake_destination) + assert result is False + + +def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): + """Tests transfer failed unlock_key.""" + # Preps + fake_wallet.coldkeypub.ss58_address = "fake_ss58_address" + fake_destination = "invalid_address" + fake_amount = Balance(15) + + mocked_is_valid_address = mocker.patch.object( + transfer, + "is_valid_bittensor_address_or_public_key", + return_value=True, + ) + + mocked_unlock_key = mocker.patch.object( + transfer, + "unlock_key", + return_value=mocker.Mock(success=False, message=""), + ) + + # Call + result = transfer.transfer_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + destination=fake_destination, + amount=fake_amount, + transfer_all=False, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=True, + ) + + # Asserts + mocked_is_valid_address.assert_called_once_with(fake_destination) + mocked_unlock_key.assert_called_once_with(fake_wallet) + assert result is False + + +def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( + subtensor, fake_wallet, mocker +): + """Tests transfer with keep_alive flag set to False and transfer_all flag set to True.""" + # Preps + fake_wallet.coldkeypub.ss58_address = "fake_ss58_address" + fake_destination = "valid_ss58_address" + fake_amount = Balance(15) + + mocked_is_valid_address = mocker.patch.object( + transfer, + "is_valid_bittensor_address_or_public_key", + return_value=True, + ) + mocked_unlock_key = mocker.patch.object( + transfer, + "unlock_key", + return_value=mocker.Mock(success=True, message="Unlocked"), + ) + mocked_get_chain_head = mocker.patch.object( + subtensor.substrate, "get_chain_head", return_value="some_block_hash" + ) + mocker.patch.object( + subtensor, + "get_balance", + return_value=1, + ) + mocked_get_existential_deposit = mocker.patch.object( + subtensor, "get_existential_deposit", return_value=1 + ) + subtensor.get_transfer_fee = mocker.patch.object( + subtensor, "get_transfer_fee", return_value=2 + ) + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call + result = transfer.transfer_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + destination=fake_destination, + amount=fake_amount, + transfer_all=True, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=False, + ) + + # Asserts + mocked_is_valid_address.assert_called_once_with(fake_destination) + mocked_unlock_key.assert_called_once_with(fake_wallet) + assert mocked_compose_call.call_count == 0 + assert mocked_sign_and_send_extrinsic.call_count == 0 + assert result is False From 9b425da75d90e04280b95a69649bc0ba1dcde3f0 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 12:22:47 -0700 Subject: [PATCH 105/177] fix `destination` parameter in tests --- tests/e2e_tests/test_transfer.py | 20 ++++++++++---------- tests/unit_tests/extrinsics/test_transfer.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index e195e79285..89613f6553 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -40,7 +40,7 @@ def test_transfer(subtensor, alice_wallet): # Transfer Tao assert subtensor.extrinsics.transfer( wallet=alice_wallet, - dest=dest_coldkey, + destination=dest_coldkey, amount=transfer_value, wait_for_finalization=True, wait_for_inclusion=True, @@ -87,7 +87,7 @@ async def test_transfer_async(async_subtensor, alice_wallet): # Transfer Tao assert await async_subtensor.extrinsics.transfer( wallet=alice_wallet, - dest=dest_coldkey, + destination=dest_coldkey, amount=transfer_value, wait_for_finalization=True, wait_for_inclusion=True, @@ -116,8 +116,8 @@ def test_transfer_all(subtensor, alice_wallet): # fund the first dummy account assert subtensor.extrinsics.transfer( - alice_wallet, - dest=dummy_account_1.coldkeypub.ss58_address, + wallet=alice_wallet, + destination=dummy_account_1.coldkeypub.ss58_address, amount=Balance.from_tao(2.0), wait_for_finalization=True, wait_for_inclusion=True, @@ -126,7 +126,7 @@ def test_transfer_all(subtensor, alice_wallet): existential_deposit = subtensor.chain.get_existential_deposit() assert subtensor.extrinsics.transfer( wallet=dummy_account_1, - dest=dummy_account_2.coldkeypub.ss58_address, + destination=dummy_account_2.coldkeypub.ss58_address, amount=None, transfer_all=True, wait_for_finalization=True, @@ -139,7 +139,7 @@ def test_transfer_all(subtensor, alice_wallet): assert balance_after == existential_deposit assert subtensor.extrinsics.transfer( wallet=dummy_account_2, - dest=alice_wallet.coldkeypub.ss58_address, + destination=alice_wallet.coldkeypub.ss58_address, amount=None, transfer_all=True, wait_for_inclusion=True, @@ -166,8 +166,8 @@ async def test_transfer_all_async(async_subtensor, alice_wallet): # fund the first dummy account assert await async_subtensor.extrinsics.transfer( - alice_wallet, - dest=dummy_account_1.coldkeypub.ss58_address, + wallet=alice_wallet, + destination=dummy_account_1.coldkeypub.ss58_address, amount=Balance.from_tao(2.0), wait_for_finalization=True, wait_for_inclusion=True, @@ -176,7 +176,7 @@ async def test_transfer_all_async(async_subtensor, alice_wallet): 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, + destination=dummy_account_2.coldkeypub.ss58_address, amount=None, transfer_all=True, wait_for_finalization=True, @@ -189,7 +189,7 @@ async def test_transfer_all_async(async_subtensor, alice_wallet): assert balance_after == existential_deposit assert await async_subtensor.extrinsics.transfer( wallet=dummy_account_2, - dest=alice_wallet.coldkeypub.ss58_address, + destination=alice_wallet.coldkeypub.ss58_address, amount=None, transfer_all=True, wait_for_inclusion=True, diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index 73a696ca9b..b4ceee33f8 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -126,7 +126,7 @@ def test_transfer_extrinsic_call_successful_with_failed_response( mocked_unlock_key.assert_called_once_with(fake_wallet) mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, - block=subtensor.substrate.get_block_number.return_value + block=subtensor.substrate.get_block_number.return_value, ) mocked_get_existential_deposit.assert_called_once_with( block=subtensor.substrate.get_block_number.return_value From c4dbcc3066a75d9a87751116426431ba2cce6192 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 14:14:18 -0700 Subject: [PATCH 106/177] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. --- bittensor/core/async_subtensor.py | 47 +--- bittensor/core/extrinsics/asyncex/root.py | 119 +-------- bittensor/core/extrinsics/root.py | 119 +-------- bittensor/core/subtensor.py | 48 +--- bittensor/core/subtensor_api/extrinsics.py | 1 - bittensor/core/subtensor_api/utils.py | 1 - migration.md | 3 +- .../extrinsics/asyncex/test_root.py | 230 ------------------ tests/unit_tests/extrinsics/test_root.py | 155 ------------ tests/unit_tests/test_async_subtensor.py | 42 ---- tests/unit_tests/test_subtensor_extended.py | 44 ---- 11 files changed, 7 insertions(+), 802 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 2a8e7bb286..347e97af36 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -62,10 +62,7 @@ register_subnet_extrinsic, set_subnet_identity_extrinsic, ) -from bittensor.core.extrinsics.asyncex.root import ( - set_root_weights_extrinsic, - root_register_extrinsic, -) +from bittensor.core.extrinsics.asyncex.root import root_register_extrinsic from bittensor.core.extrinsics.asyncex.serving import ( get_last_bonds_reset, publish_metadata, @@ -121,7 +118,6 @@ ) from bittensor.utils.weight_utils import ( generate_weight_hash, - convert_uids_and_weights, U16_MAX, ) @@ -4977,47 +4973,6 @@ async def root_register( period=period, ) - async def root_set_weights( - self, - wallet: "Wallet", - netuids: list[int], - weights: list[float], - version_key: int = 0, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, - period: Optional[int] = None, - ) -> bool: - """ - Set weights for the root network. - - Arguments: - wallet: bittensor wallet instance. - netuids: The list of subnet uids. - weights: The list of weights to be set. - version_key: Version key for compatibility with the network. Default is `0`. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to `False`. - period: 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. - - Returns: - `True` if the setting of weights is successful, `False` otherwise. - """ - netuids_, weights_ = convert_uids_and_weights(netuids, weights) - logging.info(f"Setting weights in network: [blue]{self.network}[/blue]") - # Run the set weights operation. - return await set_root_weights_extrinsic( - subtensor=self, - wallet=wallet, - netuids=netuids_, - weights=weights_, - version_key=version_key, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - period=period, - ) - async def set_children( self, wallet: "Wallet", diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index 3405ebadb8..bab6f0b230 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -1,18 +1,9 @@ import asyncio -from typing import Optional, Union, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING -import numpy as np -from numpy.typing import NDArray - -from bittensor.core.errors import SubstrateRequestException -from bittensor.utils import u16_normalized_float, format_error_message, unlock_key +from bittensor.utils import u16_normalized_float, unlock_key from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import ( - normalize_max_weight, - convert_weights_and_uids_for_emit, - convert_uids_and_weights, -) if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -146,109 +137,3 @@ async def root_register_extrinsic( # neuron not found, try again logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") return False - - -async def set_root_weights_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuids: Union[NDArray[np.int64], list[int]], - weights: Union[NDArray[np.float32], list[float]], - version_key: int = 0, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = None, -) -> bool: - """Sets the given weights and values on a chain for a wallet hotkey account. - - Arguments: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The AsyncSubtensor object - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - netuids (Union[NDArray[np.int64], list[int]]): The `netuid` of the subnet to set weights for. - weights (Union[NDArray[np.float32], list[Float]]): Weights to set. These must be `Float`s and must correspond - to the passed `netuid` s. - version_key (int): The version key of the validator. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ` - True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - 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. - - Returns: - `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, the - response is `True`. - """ - my_uid = await subtensor.substrate.query( - "SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address] - ) - - if my_uid is None: - logging.error("Your hotkey is not registered to the root network.") - return False - - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False - - # Convert types. - netuids, weights = convert_uids_and_weights(netuids, weights) - - logging.debug("[magenta]Fetching weight limits ...[/magenta]") - min_allowed_weights, max_weight_limit = await _get_limits(subtensor) - - # Get non zero values. - non_zero_weight_idx = np.argwhere(weights > 0).squeeze(axis=1) - non_zero_weights = weights[non_zero_weight_idx] - if non_zero_weights.size < min_allowed_weights: - raise ValueError( - "The minimum number of weights required to set weights is {}, got {}".format( - min_allowed_weights, non_zero_weights.size - ) - ) - - # Normalize the weights to max value. - logging.info("[magenta]Normalizing weights ...[/magenta]") - formatted_weights = normalize_max_weight(x=weights, limit=max_weight_limit) - logging.info( - f"Raw weights -> Normalized weights: [blue]{weights}[/blue] -> [green]{formatted_weights}[/green]" - ) - - try: - logging.info(":satellite: [magenta]Setting root weights...[magenta]") - weight_uids, weight_vals = convert_weights_and_uids_for_emit(netuids, weights) - - # Since this extrinsic is only for the root network, we can set netuid to 0. - netuid = 0 - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_root_weights", - call_params={ - "dests": weight_uids, - "weights": weight_vals, - "netuid": netuid, - "version_key": version_key, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - - success, message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - ) - - if success: - logging.info(":white_heavy_check_mark: [green]Finalized[/green]") - return True - - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") - return False - - except SubstrateRequestException as e: - fmt_err = format_error_message(e) - logging.error(f":cross_mark: [red]Failed error:[/red] {fmt_err}") - return False diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index 66ee850194..1606bf76da 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -1,23 +1,12 @@ import time -from typing import Optional, Union, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING -import numpy as np -from numpy.typing import NDArray - -from bittensor.core.errors import SubstrateRequestException from bittensor.utils import ( u16_normalized_float, - format_error_message, unlock_key, - torch, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import ( - normalize_max_weight, - convert_weights_and_uids_for_emit, - convert_uids_and_weights, -) if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -147,109 +136,3 @@ def root_register_extrinsic( # neuron not found, try again logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") return False - - -def set_root_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuids: Union[NDArray[np.int64], "torch.LongTensor", list[int]], - weights: Union[NDArray[np.float32], "torch.FloatTensor", list[float]], - version_key: int = 0, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = None, -) -> bool: - """Sets the given weights and values on chain for a given wallet hotkey account. - - Arguments: - subtensor (bittensor.core.subtensor.Subtensor): The Subtensor object - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - netuids (Union[NDArray[np.int64], list[int]]): The `netuid` of the subnet to set weights for. - weights (Union[NDArray[np.float32], list[float]]): Weights to set. These must be floats and must correspond - to the passed `netuid` s. - version_key (int): The version key of the validator. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - 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. - - Returns: - `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, the - response is `True`. - """ - my_uid = subtensor.substrate.query( - "SubtensorModule", "Uids", [0, wallet.hotkey.ss58_address] - ) - - if my_uid is None: - logging.error("Your hotkey is not registered to the root network.") - return False - - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False - - # Convert types. - netuids, weights = convert_uids_and_weights(netuids, weights) - - logging.debug("[magenta]Fetching weight limits ...[/magenta]") - min_allowed_weights, max_weight_limit = _get_limits(subtensor) - - # Get non zero values. - non_zero_weight_idx = np.argwhere(weights > 0).squeeze(axis=1) - non_zero_weights = weights[non_zero_weight_idx] - if non_zero_weights.size < min_allowed_weights: - raise ValueError( - "The minimum number of weights required to set weights is {}, got {}".format( - min_allowed_weights, non_zero_weights.size - ) - ) - - # Normalize the weights to max value. - logging.info("[magenta]Normalizing weights ...[/magenta]") - formatted_weights = normalize_max_weight(x=weights, limit=max_weight_limit) - logging.info( - f"Raw weights -> Normalized weights: [blue]{weights}[/blue] -> [green]{formatted_weights}[/green]" - ) - - try: - logging.info(":satellite: [magenta]Setting root weights...[magenta]") - weight_uids, weight_vals = convert_weights_and_uids_for_emit(netuids, weights) - - # Since this extrinsic is only for the root network, we can set netuid to 0. - netuid = 0 - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_root_weights", - call_params={ - "dests": weight_uids, - "weights": weight_vals, - "netuid": netuid, - "version_key": version_key, - "hotkey": wallet.hotkey.ss58_address, - }, - ) - - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - period=period, - ) - - if success: - logging.info(":white_heavy_check_mark: [green]Finalized[/green]") - return True - - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") - return False - - except SubstrateRequestException as e: - fmt_err = format_error_message(e) - logging.error(f":cross_mark: [red]Failed error:[/red] {fmt_err}") - return False diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 1a551ea490..a6c83dc96b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -65,10 +65,7 @@ register_subnet_extrinsic, set_subnet_identity_extrinsic, ) -from bittensor.core.extrinsics.root import ( - root_register_extrinsic, - set_root_weights_extrinsic, -) +from bittensor.core.extrinsics.root import root_register_extrinsic from bittensor.core.extrinsics.serving import ( get_last_bonds_reset, publish_metadata, @@ -125,7 +122,6 @@ ) from bittensor.utils.weight_utils import ( generate_weight_hash, - convert_uids_and_weights, U16_MAX, ) @@ -3808,48 +3804,6 @@ def root_set_pending_childkey_cooldown( period=period, ) - def root_set_weights( - self, - wallet: "Wallet", - netuids: list[int], - weights: list[float], - version_key: int = 0, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, - period: Optional[int] = None, - ) -> bool: - """ - Set weights for the root network. - - Arguments: - wallet (bittensor_wallet.Wallet): bittensor wallet instance. - netuids (list[int]): The list of subnet uids. - weights (list[float]): The list of weights to be set. - version_key (int, optional): Version key for compatibility with the network. Default is ``0``. - wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. Defaults to - ``False``. - wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. - Defaults to ``False``. - 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. - - Returns: - `True` if the setting of weights is successful, `False` otherwise. - """ - netuids_, weights_ = convert_uids_and_weights(netuids, weights) - logging.info(f"Setting weights in network: [blue]{self.network}[/blue]") - return set_root_weights_extrinsic( - subtensor=self, - wallet=wallet, - netuids=netuids_, - weights=weights_, - version_key=version_key, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - period=period, - ) - def set_children( self, wallet: "Wallet", diff --git a/bittensor/core/subtensor_api/extrinsics.py b/bittensor/core/subtensor_api/extrinsics.py index bc24f9f349..c2acf078e5 100644 --- a/bittensor/core/subtensor_api/extrinsics.py +++ b/bittensor/core/subtensor_api/extrinsics.py @@ -19,7 +19,6 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.remove_liquidity = subtensor.remove_liquidity self.reveal_weights = subtensor.reveal_weights self.root_register = subtensor.root_register - self.root_set_weights = subtensor.root_set_weights self.root_set_pending_childkey_cooldown = ( subtensor.root_set_pending_childkey_cooldown ) diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 67399a37ed..b8383a3f62 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -153,7 +153,6 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.root_set_pending_childkey_cooldown = ( subtensor._subtensor.root_set_pending_childkey_cooldown ) - subtensor.root_set_weights = subtensor._subtensor.root_set_weights subtensor.serve_axon = subtensor._subtensor.serve_axon subtensor.set_children = subtensor._subtensor.set_children subtensor.set_commitment = subtensor._subtensor.set_commitment diff --git a/migration.md b/migration.md index 497ab042a9..25edf20b71 100644 --- a/migration.md +++ b/migration.md @@ -158,4 +158,5 @@ It must include: - [x] `_do_pow_register` logic is included in the main code `.register_extrinsic` - [x] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` - [x] `._do_transfer` logic is included in the main code `.transfer_extrinsic` -- [x] `dest` parameter has been renamed to `destination` in `transfer_extrinsic` function and `subtensor.transfer` method. \ No newline at end of file +- [x] `dest` parameter has been renamed to `destination` in `transfer_extrinsic` function and `subtensor.transfer` method. +- [x]] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_root.py b/tests/unit_tests/extrinsics/asyncex/test_root.py index b4c32a1e87..70acf9857c 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_root.py +++ b/tests/unit_tests/extrinsics/asyncex/test_root.py @@ -321,233 +321,3 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc params=[0, "fake_hotkey_address"], ) assert result is False - - -@pytest.mark.asyncio -async def test_set_root_weights_extrinsic_success(subtensor, fake_wallet, mocker): - """Tests successful setting of root weights.""" - fake_wallet.hotkey.ss58_address = "fake_hotkey" - netuids = [1, 2, 3] - weights = [0.1, 0.2, 0.7] - - mocker.patch.object(subtensor.substrate, "query", return_value=123) - mocker.patch.object( - async_root, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocker.patch.object(async_root, "_get_limits", return_value=(2, 1.0)) - mocker.patch.object(async_root, "normalize_max_weight", return_value=weights) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - return_value=(True, ""), - ) - - result = await async_root.set_root_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuids=netuids, - weights=weights, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - use_nonce=True, - ) - assert result is True - - -@pytest.mark.asyncio -async def test_set_root_weights_extrinsic_no_waiting(subtensor, fake_wallet, mocker): - """Tests setting root weights without waiting for inclusion or finalization.""" - fake_wallet.hotkey.ss58_address = "fake_hotkey" - netuids = [1, 2, 3] - weights = [0.1, 0.2, 0.7] - - mocker.patch.object(subtensor.substrate, "query", return_value=123) - mocker.patch.object( - async_root, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocker.patch.object(async_root, "_get_limits", return_value=(2, 1.0)) - mocker.patch.object(async_root, "normalize_max_weight", return_value=weights) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - return_value=(True, ""), - ) - - result = await async_root.set_root_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuids=netuids, - weights=weights, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=False, - wait_for_finalization=False, - period=None, - use_nonce=True, - ) - assert result is True - - -@pytest.mark.asyncio -async def test_set_root_weights_extrinsic_not_registered( - subtensor, fake_wallet, mocker -): - """Tests failure when hotkey is not registered.""" - fake_wallet.hotkey.ss58_address = "fake_hotkey" - - mocker.patch.object(subtensor.substrate, "query", return_value=None) - - result = await async_root.set_root_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuids=[1, 2, 3], - weights=[0.1, 0.2, 0.7], - ) - - assert result is False - - -@pytest.mark.asyncio -async def test_set_root_weights_extrinsic_insufficient_weights( - subtensor, fake_wallet, mocker -): - """Tests failure when number of weights is less than the minimum allowed.""" - fake_wallet.hotkey.ss58_address = "fake_hotkey" - netuids = [1, 2] - weights = [0.5, 0.5] - - mocker.patch.object(subtensor.substrate, "query", return_value=123) - mocker.patch.object( - async_root, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocker.patch.object(async_root, "_get_limits", return_value=(3, 1.0)) - - with pytest.raises(ValueError): - await async_root.set_root_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuids=netuids, - weights=weights, - ) - - -@pytest.mark.asyncio -async def test_set_root_weights_extrinsic_unlock_failed(subtensor, fake_wallet, mocker): - """Tests failure due to unlock key error.""" - fake_wallet.hotkey.ss58_address = "fake_hotkey" - - mocker.patch.object(subtensor.substrate, "query", return_value=123) - mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=False, message="Unlock failed"), - ) - - result = await async_root.set_root_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuids=[1, 2, 3], - weights=[0.1, 0.2, 0.7], - ) - - assert result is False - - -@pytest.mark.asyncio -async def test_set_root_weights_extrinsic_transaction_failed( - subtensor, fake_wallet, mocker -): - """Tests failure when transaction is not successful.""" - fake_wallet.hotkey.ss58_address = "fake_hotkey" - - mocker.patch.object(subtensor.substrate, "query", return_value=123) - mocker.patch.object( - async_root, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocker.patch.object(async_root, "_get_limits", return_value=(2, 1.0)) - mocker.patch.object( - async_root, "normalize_max_weight", return_value=[0.1, 0.2, 0.7] - ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - return_value=(True, ""), - ) - - result = await async_root.set_root_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuids=[1, 2, 3], - weights=[0.1, 0.2, 0.7], - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - use_nonce=True, - ) - assert result is True - - -@pytest.mark.asyncio -async def test_set_root_weights_extrinsic_request_exception( - subtensor, fake_wallet, mocker -): - """Tests failure due to SubstrateRequestException.""" - fake_wallet.hotkey.ss58_address = "fake_hotkey" - - mocker.patch.object(subtensor.substrate, "query", return_value=123) - mocker.patch.object( - async_root, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocker.patch.object(async_root, "_get_limits", return_value=(2, 1.0)) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") - mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, - "sign_and_send_extrinsic", - side_effect=SubstrateRequestException("Request failed"), - ) - mocked_format_error_message = mocker.patch.object( - async_root, "format_error_message" - ) - - result = await async_root.set_root_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuids=[1, 2, 3], - weights=[0.1, 0.2, 0.7], - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - assert result is False - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - use_nonce=True, - ) - mocked_format_error_message.assert_called_once() diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index e3ef90cf0e..5a16a10909 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -130,158 +130,3 @@ def test_root_register_extrinsic_insufficient_balance( block=mock_subtensor.get_current_block.return_value, ) mock_subtensor.substrate.submit_extrinsic.assert_not_called() - - -@pytest.mark.parametrize( - "wait_for_inclusion, wait_for_finalization, netuids, weights, expected_success", - [ - (True, False, [1, 2], [0.5, 0.5], True), # Success - weights set - ( - False, - False, - [1, 2], - [0.5, 0.5], - True, - ), # Success - weights set no wait - ( - True, - False, - [1, 2], - [2000, 20], - True, - ), # Success - large value to be normalized - ( - True, - False, - [1, 2], - [2000, 0], - True, - ), # Success - single large value - ( - True, - False, - [1, 2], - [0.5, 0.5], - False, - ), # Failure - setting weights failed - ], - ids=[ - "success-weights-set", - "success-not-wait", - "success-large-value", - "success-single-value", - "failure-setting-weights", - ], -) -def test_set_root_weights_extrinsic( - mock_subtensor, - mock_wallet, - wait_for_inclusion, - wait_for_finalization, - netuids, - weights, - expected_success, - mocker, -): - # Preps - mocked_compose_call = mocker.patch.object(mock_subtensor.substrate, "compose_call") - mocked_sign_and_send_extrinsic = mocker.patch.object( - mock_subtensor, - "sign_and_send_extrinsic", - return_value=(expected_success, "Mock error"), - ) - mocker.patch.object( - root, - "_get_limits", - return_value=(0, 1), - ) - - # Call - result = root.set_root_weights_extrinsic( - subtensor=mock_subtensor, - wallet=mock_wallet, - netuids=netuids, - weights=weights, - version_key=0, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, - wallet=mock_wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=None, - use_nonce=True, - ) - # Asserts - assert result == expected_success - - -@pytest.mark.parametrize( - "wait_for_inclusion, wait_for_finalization, netuids, weights, user_response, expected_success", - [ - (True, False, [1, 2], [0.5, 0.5], True, True), # Success - weights set - ( - False, - False, - [1, 2], - [0.5, 0.5], - None, - True, - ), # Success - weights set no wait - ( - True, - False, - [1, 2], - [2000, 20], - True, - True, - ), # Success - large value to be normalized - ( - True, - False, - [1, 2], - [2000, 0], - True, - True, - ), # Success - single large value - ( - True, - False, - [1, 2], - [0.5, 0.5], - None, - False, - ), # Failure - setting weights failed - ], - ids=[ - "success-weights-set", - "success-not-wait", - "success-large-value", - "success-single-value", - "failure-setting-weights", - ], -) -def test_set_root_weights_extrinsic_torch( - mock_subtensor, - mock_wallet, - wait_for_inclusion, - wait_for_finalization, - netuids, - weights, - user_response, - expected_success, - force_legacy_torch_compatible_api, - mocker, -): - test_set_root_weights_extrinsic( - mock_subtensor, - mock_wallet, - wait_for_inclusion, - wait_for_finalization, - netuids, - weights, - expected_success, - mocker, - ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 61ea4b0b0c..d75c5c3604 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2853,48 +2853,6 @@ async def test_set_weights_with_exception(subtensor, fake_wallet, mocker): assert message == "No attempt made. Perhaps it is too soon to set weights!" -@pytest.mark.asyncio -async def test_root_set_weights_success(subtensor, fake_wallet, mocker): - """Tests root_set_weights when the setting of weights is successful.""" - # Preps - fake_netuids = [1, 2, 3] - fake_weights = [0.3, 0.5, 0.2] - - mocked_set_root_weights_extrinsic = mocker.AsyncMock() - mocker.patch.object( - async_subtensor, "set_root_weights_extrinsic", mocked_set_root_weights_extrinsic - ) - - mocked_np_array_netuids = mocker.Mock(autospec=async_subtensor.np.ndarray) - mocked_np_array_weights = mocker.Mock(autospec=async_subtensor.np.ndarray) - mocker.patch.object( - async_subtensor.np, - "array", - side_effect=[mocked_np_array_netuids, mocked_np_array_weights], - ) - - # Call - result = await subtensor.root_set_weights( - wallet=fake_wallet, - netuids=fake_netuids, - weights=fake_weights, - ) - - # Asserts - mocked_set_root_weights_extrinsic.assert_awaited_once() - mocked_set_root_weights_extrinsic.assert_called_once_with( - subtensor=subtensor, - wallet=fake_wallet, - netuids=mocked_np_array_netuids, - weights=mocked_np_array_weights, - version_key=0, - wait_for_finalization=True, - wait_for_inclusion=True, - period=None, - ) - assert result == mocked_set_root_weights_extrinsic.return_value - - @pytest.mark.asyncio async def test_commit_weights_success(subtensor, fake_wallet, mocker): """Tests commit_weights when the weights are committed successfully.""" diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 8869084668..1a53e4e7ba 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1296,50 +1296,6 @@ def test_root_register_is_already_registered( mock_substrate.submit_extrinsic.assert_not_called() -def test_root_set_weights_no_uid(mock_substrate, subtensor, fake_wallet, mocker): - mock_substrate.query.return_value = None - - success = subtensor.root_set_weights( - fake_wallet, - netuids=[1, 2], - weights=[0.5, 0.5], - ) - - assert success is False - - mock_substrate.query.assert_called_once_with( - "SubtensorModule", - "Uids", - [0, fake_wallet.hotkey.ss58_address], - ) - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_root_set_weights_min_allowed_weights( - mock_substrate, subtensor, fake_wallet, mocker -): - mocker.patch.object( - subtensor, - "get_hyperparameter", - autospec=True, - return_value=5, - ) - mock_substrate.query.return_value = 1 - - with pytest.raises( - ValueError, - match="The minimum number of weights required to set weights is 5, got 2", - ): - subtensor.root_set_weights( - fake_wallet, - netuids=[1, 2], - weights=[0.5, 0.5], - ) - - subtensor.get_hyperparameter.assert_any_call("MinAllowedWeights", netuid=0) - mock_substrate.submit_extrinsic.assert_not_called() - - def test_sign_and_send_extrinsic(mock_substrate, subtensor, fake_wallet, mocker): call = mocker.Mock() From 871ac9e0bdcf24e7d1b96424b36fee4e81de14d7 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 14:00:23 -0700 Subject: [PATCH 107/177] `set_children_extrinsic` and `root_set_pending_childkey_cooldown_extrinsic` --- bittensor/core/async_subtensor.py | 45 +++++++++------- bittensor/core/extrinsics/asyncex/children.py | 53 +++++++++++++------ bittensor/core/extrinsics/children.py | 49 +++++++++++------ bittensor/core/subtensor.py | 43 ++++++++------- migration.md | 15 ++++-- .../extrinsics/asyncex/test_children.py | 9 ++-- tests/unit_tests/extrinsics/test_children.py | 7 +-- 7 files changed, 137 insertions(+), 84 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 347e97af36..2055b5ebe7 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4870,8 +4870,9 @@ async def reveal_weights( and be rejected. You can think of it as an expiration date for the transaction. Returns: - tuple[bool, str]: `True` if the weight revelation is successful, False otherwise. And `msg`, a string - value describing the success or potential error. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. This function allows subnet validators to reveal their previously committed weight vector. @@ -4908,25 +4909,27 @@ async def root_set_pending_childkey_cooldown( self, wallet: "Wallet", cooldown: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> tuple[bool, str]: """Sets the pending childkey cooldown. - Arguments: + Parameters: wallet: bittensor wallet instance. cooldown: the number of blocks to setting pending childkey cooldown. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is - `False`. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. Note: This operation can only be successfully performed if your wallet has root privileges. """ @@ -4934,9 +4937,10 @@ async def root_set_pending_childkey_cooldown( subtensor=self, wallet=wallet, cooldown=cooldown, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) # TODO: remove `block_hash` argument @@ -4979,29 +4983,30 @@ async def set_children( hotkey: str, netuid: int, children: list[tuple[float, str]], + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - raise_error: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Allows a coldkey to set children-keys. - Arguments: + Parameters: wallet: bittensor wallet instance. hotkey: The `SS58` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. Raises: DuplicateChild: There are duplicates in the list of children. @@ -5022,10 +5027,10 @@ async def set_children( hotkey=hotkey, netuid=netuid, children=children, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - raise_error=raise_error, - period=period, ) async def set_delegate_take( diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 46853642fe..7476ef8fb8 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -12,30 +12,31 @@ async def set_children_extrinsic( hotkey: str, netuid: int, children: list[tuple[float, str]], - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - raise_error: bool = False, period: Optional[int] = None, -): + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> tuple[bool, str]: """ Allows a coldkey to set children-keys. - Arguments: - subtensor: bittensor subtensor. + Parameters: + subtensor: The Subtensor client instance used for blockchain interaction. wallet: bittensor wallet instance. hotkey: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the operation, - and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. Raises: DuplicateChild: There are duplicates in the list of children. @@ -75,10 +76,10 @@ async def set_children_extrinsic( success, message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - raise_error=raise_error, - period=period, ) if not wait_for_finalization and not wait_for_inclusion: @@ -94,12 +95,29 @@ async def root_set_pending_childkey_cooldown_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", cooldown: int, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ - Allows a coldkey to set children-keys. + Allows a root coldkey to set children-keys. + + Parameters: + subtensor: The Subtensor client instance used for blockchain interaction. + wallet: The wallet used to sign the extrinsic (must be unlocked). + cooldown: The cooldown period in blocks. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + + Returns: + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet) @@ -122,9 +140,10 @@ async def root_set_pending_childkey_cooldown_extrinsic( success, message = await subtensor.sign_and_send_extrinsic( call=sudo_call, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index dd91fbe97b..5f7c1a9d84 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -12,30 +12,31 @@ def set_children_extrinsic( hotkey: str, netuid: int, children: list[tuple[float, str]], - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - raise_error: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ): """ Allows a coldkey to set children-keys. - Arguments: - subtensor: bittensor subtensor. + Parameters: + subtensor: The Subtensor client instance used for blockchain interaction. wallet: bittensor wallet instance. hotkey: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the operation, - and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. Raises: DuplicateChild: There are duplicates in the list of children. @@ -74,10 +75,10 @@ def set_children_extrinsic( success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - raise_error=raise_error, - period=period, ) if not wait_for_finalization and not wait_for_inclusion: @@ -93,12 +94,29 @@ def root_set_pending_childkey_cooldown_extrinsic( subtensor: "Subtensor", wallet: "Wallet", cooldown: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ - Allows a coldkey to set children-keys. + Allows a root coldkey to set children-keys. + + Parameters: + subtensor: The Subtensor client instance used for blockchain interaction. + wallet: The wallet used to sign the extrinsic (must be unlocked). + cooldown: The cooldown period in blocks. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + + Returns: + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet) @@ -120,9 +138,10 @@ def root_set_pending_childkey_cooldown_extrinsic( success, message = subtensor.sign_and_send_extrinsic( call=sudo_call, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a6c83dc96b..495c391b5e 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3773,25 +3773,27 @@ def root_set_pending_childkey_cooldown( self, wallet: "Wallet", cooldown: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> tuple[bool, str]: """Sets the pending childkey cooldown. - Arguments: + Parameters: wallet: bittensor wallet instance. cooldown: the number of blocks to setting pending childkey cooldown. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``False``. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is - ``False``. 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion (bool): Waits for the transaction to be included in a block. + wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. Note: This operation can only be successfully performed if your wallet has root privileges. """ @@ -3799,9 +3801,10 @@ def root_set_pending_childkey_cooldown( subtensor=self, wallet=wallet, cooldown=cooldown, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def set_children( @@ -3810,30 +3813,30 @@ def set_children( hotkey: str, netuid: int, children: list[tuple[float, str]], + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - raise_error: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Allows a coldkey to set children-keys. - Arguments: + Parameters: wallet: bittensor wallet instance. hotkey: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. - + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ return set_children_extrinsic( subtensor=self, @@ -3841,10 +3844,10 @@ def set_children( hotkey=hotkey, netuid=netuid, children=children, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - raise_error=raise_error, - period=period, ) def set_delegate_take( @@ -3861,7 +3864,7 @@ def set_delegate_take( Sets the delegate 'take' percentage for a neuron identified by its hotkey. The 'take' represents the percentage of rewards that the delegate claims from its nominators' stakes. - Arguments: + Parameters: wallet (bittensor_wallet.Wallet): bittensor wallet instance. hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. take (float): Percentage reward for the delegate. diff --git a/migration.md b/migration.md index 25edf20b71..16582ec6b0 100644 --- a/migration.md +++ b/migration.md @@ -1,7 +1,7 @@ # Plan ## Extrinsics and related -1. Standardize parameter order across all extrinsics and related calls. Pass extrinsic-specific arguments first (e.g., wallet, hotkey, netuid, amount), followed by optional general flags (e.g., wait_for_inclusion, wait_for_finalization) +1. ✅ Standardize parameter order across all extrinsics and related calls. Pass extrinsic-specific arguments first (e.g., wallet, hotkey, netuid, amount), followed by optional general flags (e.g., wait_for_inclusion, wait_for_finalization)
Example @@ -35,9 +35,9 @@ allow_partial_stake: bool = False, safe_staking: bool = False, period: Optional[int] = None, + raise_error: bool = True, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - raise_error: bool = True, ) -> bool: ```
@@ -48,8 +48,8 @@ - Ease of processing - This class should contain success, message, and optionally data and logs. (to save all logs during the extrinsic) -3. Set `wait_for_inclusion` and `wait_for_finalization` to `True` by default in extrinsics and their related calls. Then we will guarantee the correct/expected extrinsic call response is consistent with the chain response. If the user changes those values, then it is the user's responsibility. -4. Make the internal logic of extrinsics the same. There are extrinsics that are slightly different in implementation. +3. ✅ Set `wait_for_inclusion` and `wait_for_finalization` to `True` by default in extrinsics and their related calls. Then we will guarantee the correct/expected extrinsic call response is consistent with the chain response. If the user changes those values, then it is the user's responsibility. +4. ✅ Make the internal logic of extrinsics the same. There are extrinsics that are slightly different in implementation. 5. Since SDK is not a responsible tool, try to remove all calculations inside extrinsics that do not affect the result, but are only used in logging. Actually, this should be applied not to extrinsics only but for all codebase. @@ -159,4 +159,9 @@ It must include: - [x] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` - [x] `._do_transfer` logic is included in the main code `.transfer_extrinsic` - [x] `dest` parameter has been renamed to `destination` in `transfer_extrinsic` function and `subtensor.transfer` method. -- [x]] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. \ No newline at end of file +- [x]] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. + +# Standardize parameter order is applied for (extrinsics and related calls): +Note: `raise_error` parameter is included in the list of parameters and parameters order is standardized. +- [x] `.set_children_extrinsic` and `.root_set_pending_childkey_cooldown_extrinsic`. `subtensor.set_children` and `subtensor.root_set_pending_childkey_cooldown` methods. +- \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_children.py b/tests/unit_tests/extrinsics/asyncex/test_children.py index da107dfad4..48c0940170 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_children.py +++ b/tests/unit_tests/extrinsics/asyncex/test_children.py @@ -50,10 +50,10 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): mocked_sign_and_send_extrinsic.assert_awaited_once_with( call=substrate.compose_call.return_value, wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=False, period=None, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, ) assert success is True @@ -86,9 +86,10 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( mocked_sign_and_send_extrinsic.assert_awaited_once_with( call=substrate.compose_call.return_value, wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=False, period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, ) assert success is True assert "Success" in message diff --git a/tests/unit_tests/extrinsics/test_children.py b/tests/unit_tests/extrinsics/test_children.py index 2af3517fff..12bb96f09b 100644 --- a/tests/unit_tests/extrinsics/test_children.py +++ b/tests/unit_tests/extrinsics/test_children.py @@ -46,10 +46,10 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): mocked_sign_and_send_extrinsic.assert_called_once_with( call=subtensor.substrate.compose_call.return_value, wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=False, period=None, raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, ) assert success is True @@ -78,9 +78,10 @@ def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wa mocked_sign_and_send_extrinsic.assert_called_once_with( call=subtensor.substrate.compose_call.return_value, wallet=fake_wallet, + period=None, + raise_error=False, wait_for_inclusion=True, wait_for_finalization=False, - period=None, ) assert success is True assert "Success" in message From 3547a93c3452fa2370c6347d9dfe2f9845c4debb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:16:08 -0700 Subject: [PATCH 108/177] `.commit_reveal_extrinsic` and `subtensor.set_weights` + tests --- bittensor/core/async_subtensor.py | 37 ++++++------ .../core/extrinsics/asyncex/commit_reveal.py | 34 ++++++----- bittensor/core/extrinsics/commit_reveal.py | 32 ++++++----- bittensor/core/subtensor.py | 57 +++++++++++-------- migration.md | 7 ++- .../extrinsics/asyncex/test_commit_reveal.py | 3 + .../extrinsics/test_commit_reveal.py | 3 + tests/unit_tests/test_subtensor.py | 2 + 8 files changed, 103 insertions(+), 72 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 2055b5ebe7..8ea2bc31f3 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5169,12 +5169,14 @@ async def set_weights( netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + block_time: float = 12.0, + commit_reveal_version: int = 4, + max_retries: int = 5, version_key: int = version_as_int, + period: Optional[int] = 8, + raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - max_retries: int = 5, - block_time: float = 12.0, - period: Optional[int] = 8, ): """ Sets the weight vector for a neuron acting as a validator, specifying the weights assigned to subnet miners @@ -5184,26 +5186,27 @@ async def set_weights( work. These weight vectors are used by the Yuma Consensus algorithm to compute emissions for both validators and miners. - Arguments: + Parameters: wallet: The wallet associated with the subnet validator setting the weights. netuid: The unique identifier of the subnet. uids: The list of subnet miner neuron UIDs that the weights are being set for. weights: The corresponding weights to be set for each UID, representing the validator's evaluation of each miner's performance. - version_key: Version key for compatibility with the network. Default is int representation of - the Bittensor version. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is - `False`. - max_retries: The number of maximum attempts to set weights. Default is `5`. - block_time: The number of seconds for block duration. Default is 12.0 seconds. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the chain commit-reveal protocol to use. + max_retries: The number of maximum attempts to set weights. + version_key: Version key for compatibility with the network. period: 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. Default is 8. + and be rejected. You can think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: `True` if the setting of weights is successful, False otherwise. And `msg`, a string - value describing the success or potential error. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. This function is crucial in the Yuma Consensus mechanism, where each validator's weight vector contributes to the overall weight matrix used to calculate emissions and maintain network consensus. @@ -5249,11 +5252,13 @@ async def _blocks_weight_limit() -> bool: netuid=netuid, uids=uids, weights=weights, + block_time=block_time, + commit_reveal_version=commit_reveal_version, version_key=version_key, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - block_time=block_time, - period=period, ) retries += 1 return success, message diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py index 78a7ef4824..fd1bd00f53 100644 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ b/bittensor/core/extrinsics/asyncex/commit_reveal.py @@ -22,34 +22,37 @@ async def commit_reveal_extrinsic( netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + block_time: Union[int, float] = 12.0, + commit_reveal_version: int = 4, version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - block_time: Union[int, float] = 12.0, - period: Optional[int] = None, - commit_reveal_version: int = 4, ) -> tuple[bool, str]: """ Commits and reveals weights for a given subtensor and wallet with provided uids and weights. - Arguments: - subtensor: The AsyncSubtensor instance. + Parameters: + subtensor: The Subtensor instance. wallet: The wallet to use for committing and revealing. netuid: The id of the network. uids: The uids to commit. weights: The weights associated with the uids. - version_key: The version key to use for committing and revealing. Default is version_as_int. - wait_for_inclusion: Whether to wait for the inclusion of the transaction. Default is False. - wait_for_finalization: Whether to wait for the finalization of the transaction. Default is False. - block_time (float): The number of seconds for block duration. Default is 12.0 seconds. - 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. - commit_reveal_version: The version of the chain commit-reveal protocol to use. Default is ``4``. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the chain commit-reveal protocol to use. + version_key: The version key to use for committing and revealing. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure, and the second - element is a message associated with the result + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ try: uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -96,6 +99,7 @@ async def commit_reveal_extrinsic( wait_for_finalization=wait_for_finalization, sign_with="hotkey", period=period, + raise_error=raise_error, ) if not success: diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index 2f83229b33..997b13b410 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -22,34 +22,37 @@ def commit_reveal_extrinsic( netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + block_time: Union[int, float] = 12.0, + commit_reveal_version: int = 4, version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - block_time: Union[int, float] = 12.0, - period: Optional[int] = None, - commit_reveal_version: int = 4, ) -> tuple[bool, str]: """ Commits and reveals weights for a given subtensor and wallet with provided uids and weights. - Arguments: + Parameters: subtensor: The Subtensor instance. wallet: The wallet to use for committing and revealing. netuid: The id of the network. uids: The uids to commit. weights: The weights associated with the uids. - version_key: The version key to use for committing and revealing. Default is version_as_int. - wait_for_inclusion: Whether to wait for the inclusion of the transaction. Default is False. - wait_for_finalization: Whether to wait for the finalization of the transaction. Default is False. - block_time (float): The number of seconds for block duration. Default is 12.0 seconds. - 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. - commit_reveal_version: The version of the chain commit-reveal protocol to use. Default is ``4``. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the chain commit-reveal protocol to use. + version_key: The version key to use for committing and revealing. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure, and the second - element is a message associated with the result + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ try: uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -96,6 +99,7 @@ def commit_reveal_extrinsic( wait_for_finalization=wait_for_finalization, sign_with="hotkey", period=period, + raise_error=raise_error, ) if not success: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 495c391b5e..ab0caede95 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3985,40 +3985,47 @@ def set_weights( netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], + block_time: float = 12.0, + commit_reveal_version: int = 4, + max_retries: int = 5, version_key: int = version_as_int, + period: Optional[int] = 8, + raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - max_retries: int = 5, - block_time: float = 12.0, - period: Optional[int] = 8, ) -> tuple[bool, str]: """ - Sets the interneuronal weights for the specified neuron. This process involves specifying the influence or - trust a neuron places on other neurons in the network, which is a fundamental aspect of Bittensor's - decentralized learning architecture. + Sets the interneuronal weights for the specified neuron. This process involves specifying the influence or trust + a neuron places on other neurons in the network, which is a fundamental aspect of Bittensor's decentralized + learning architecture. - Arguments: - wallet: The wallet associated with the neuron setting the weights. + Parameters: + wallet: The wallet associated with the subnet validator setting the weights. netuid: The unique identifier of the subnet. - uids: The list of neuron UIDs that the weights are being set for. - weights: The corresponding weights to be set for each UID. - version_key: Version key for compatibility with the network. Default is int representation of a Bittensor - version. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is ``False``. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is ``False``. - max_retries: The number of maximum attempts to set weights. Default is ``5``. - block_time: The number of seconds for block duration. Default is 12.0 seconds. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + uids: The list of subnet miner neuron UIDs that the weights are being set for. + weights: The corresponding weights to be set for each UID, representing the validator's evaluation of each + miner's performance. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the chain commit-reveal protocol to use. + max_retries: The number of maximum attempts to set weights. + version_key: Version key for compatibility with the network. + period: 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. Default is 8. + and be rejected. You can think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple: - `True` if the setting of weights is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. This function is crucial in shaping the network's collective intelligence, where each neuron's learning and - contribution are influenced by the weights it sets towards others. + contribution are influenced by the weights it sets towards others. + + Notes: + See """ def _blocks_weight_limit() -> bool: @@ -4051,11 +4058,13 @@ def _blocks_weight_limit() -> bool: netuid=netuid, uids=uids, weights=weights, + block_time=block_time, + commit_reveal_version=commit_reveal_version, version_key=version_key, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - block_time=block_time, - period=period, ) retries += 1 return success, message diff --git a/migration.md b/migration.md index 16582ec6b0..85ac84c77b 100644 --- a/migration.md +++ b/migration.md @@ -106,7 +106,8 @@ rename this variable in documentation. 11. Remove `bittensor.utils.version.version_checking` 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. -13. ✅ The SDK is dropping support for `Python 3.9` starting with this release.~~ +13. ✅ The SDK is dropping support for `Python 3.9` starting with this release. +14. Remove `Default is` and `Default to` in docstrings bc parameters enough. ## 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`) @@ -159,9 +160,9 @@ It must include: - [x] `._do_set_root_weights` logic is included in the main code `.set_root_weights_extrinsic` - [x] `._do_transfer` logic is included in the main code `.transfer_extrinsic` - [x] `dest` parameter has been renamed to `destination` in `transfer_extrinsic` function and `subtensor.transfer` method. -- [x]] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. +- [x] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. # Standardize parameter order is applied for (extrinsics and related calls): Note: `raise_error` parameter is included in the list of parameters and parameters order is standardized. - [x] `.set_children_extrinsic` and `.root_set_pending_childkey_cooldown_extrinsic`. `subtensor.set_children` and `subtensor.root_set_pending_childkey_cooldown` methods. -- \ No newline at end of file +- [x] `.commit_reveal_extrinsic` and `subtensor.set_weights` diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index 7b2a74aeb9..7e46e436be 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -121,6 +121,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( wait_for_finalization=True, sign_with="hotkey", period=None, + raise_error=False, ) @@ -176,6 +177,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_numpy( wait_for_finalization=False, sign_with="hotkey", period=None, + raise_error=False, ) @@ -234,6 +236,7 @@ async def test_commit_reveal_v3_extrinsic_response_false( wait_for_finalization=True, sign_with="hotkey", period=None, + raise_error=False, ) diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index b0c2da977c..e9490bcbc4 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -119,6 +119,7 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( wait_for_finalization=True, sign_with="hotkey", period=None, + raise_error=False, ) @@ -173,6 +174,7 @@ def test_commit_reveal_v3_extrinsic_success_with_numpy( wait_for_finalization=False, sign_with="hotkey", period=None, + raise_error=False, ) @@ -230,6 +232,7 @@ def test_commit_reveal_v3_extrinsic_response_false( wait_for_finalization=True, sign_with="hotkey", period=None, + raise_error=False, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 76a211b782..5f3125a3f5 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3180,11 +3180,13 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): netuid=fake_netuid, uids=fake_uids, weights=fake_weights, + commit_reveal_version=4, version_key=subtensor_module.version_as_int, wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, block_time=12.0, period=8, + raise_error=False, ) assert result == mocked_commit_reveal_v3_extrinsic.return_value From 049810b17b29ebf26fe115d5d4977eb7a9e3d3fb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:24:32 -0700 Subject: [PATCH 109/177] `wait_for_inclusion = True` and `wait_for_finalization = True` for `subtensor.set_weights` since this is extrinsic --- bittensor/core/async_subtensor.py | 4 ++-- bittensor/core/subtensor.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 8ea2bc31f3..1c6f81ca54 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5175,8 +5175,8 @@ async def set_weights( version_key: int = version_as_int, period: Optional[int] = 8, raise_error: bool = False, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ): """ Sets the weight vector for a neuron acting as a validator, specifying the weights assigned to subnet miners diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ab0caede95..bc61f660b6 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3991,8 +3991,8 @@ def set_weights( version_key: int = version_as_int, period: Optional[int] = 8, raise_error: bool = False, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ Sets the interneuronal weights for the specified neuron. This process involves specifying the influence or trust From 07105bce9de3530d91a264c3ae7f73b319e16a8d Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:27:44 -0700 Subject: [PATCH 110/177] test --- tests/unit_tests/test_async_subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index d75c5c3604..a267f8c95f 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2799,8 +2799,8 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): netuid=fake_netuid, uids=fake_uids, version_key=async_subtensor.version_as_int, - wait_for_finalization=False, - wait_for_inclusion=False, + wait_for_finalization=True, + wait_for_inclusion=True, weights=fake_weights, period=8, ) From 0ef19bf1c326855c554cd69063ff9b7c87637675 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:39:50 -0700 Subject: [PATCH 111/177] `.add_liquidity_extrinsic` and `subtensor.add_liquidity` --- bittensor/core/async_subtensor.py | 20 +++++++------- .../core/extrinsics/asyncex/liquidity.py | 27 ++++++++++--------- bittensor/core/extrinsics/liquidity.py | 27 ++++++++++--------- bittensor/core/subtensor.py | 20 +++++++------- migration.md | 1 + .../extrinsics/asyncex/test_liquidity.py | 3 ++- tests/unit_tests/extrinsics/test_liquidity.py | 3 ++- tests/unit_tests/test_async_subtensor.py | 3 ++- tests/unit_tests/test_subtensor.py | 3 ++- 9 files changed, 61 insertions(+), 46 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1c6f81ca54..9cc096419d 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4370,26 +4370,27 @@ async def add_liquidity( price_low: Balance, price_high: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ Adds liquidity to the specified price range. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. liquidity: The amount of liquidity to be added. price_low: The lower bound of the price tick range. In TAO. price_high: The upper bound of the price tick range. In TAO. - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to - `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -4397,7 +4398,7 @@ async def add_liquidity( - False and an error message if the submission fails or the wallet cannot be unlocked. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call ``toggle_user_liquidity`` - method to enable/disable user liquidity. + method to enable/disable user liquidity. """ return await add_liquidity_extrinsic( subtensor=self, @@ -4407,9 +4408,10 @@ async def add_liquidity( price_low=price_low, price_high=price_high, hotkey=hotkey, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def add_stake_multiple( diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index 8c41e1b66b..d9a6fec11b 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -18,26 +18,28 @@ async def add_liquidity_extrinsic( price_low: Balance, price_high: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ Adds liquidity to the specified price range. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. liquidity: The amount of liquidity to be added. price_low: The lower bound of the price tick range. price_high: The upper bound of the price tick range. - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -73,6 +75,7 @@ async def add_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, + raise_error=raise_error, ) @@ -89,7 +92,7 @@ async def modify_liquidity_extrinsic( ) -> tuple[bool, str]: """Modifies liquidity in liquidity position by adding or removing liquidity from it. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. @@ -147,7 +150,7 @@ async def remove_liquidity_extrinsic( ) -> tuple[bool, str]: """Remove liquidity and credit balances back to wallet's hotkey stake. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. @@ -202,7 +205,7 @@ async def toggle_user_liquidity_extrinsic( ) -> tuple[bool, str]: """Allow to toggle user liquidity for specified subnet. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index 96e502692c..62a0d0e0e9 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -18,26 +18,28 @@ def add_liquidity_extrinsic( price_low: Balance, price_high: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ Adds liquidity to the specified price range. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. liquidity: The amount of liquidity to be added. price_low: The lower bound of the price tick range. price_high: The upper bound of the price tick range. - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -73,6 +75,7 @@ def add_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, + raise_error=raise_error, ) @@ -89,7 +92,7 @@ def modify_liquidity_extrinsic( ) -> tuple[bool, str]: """Modifies liquidity in liquidity position by adding or removing liquidity from it. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. @@ -147,7 +150,7 @@ def remove_liquidity_extrinsic( ) -> tuple[bool, str]: """Remove liquidity and credit balances back to wallet's hotkey stake. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. @@ -202,7 +205,7 @@ def toggle_user_liquidity_extrinsic( ) -> tuple[bool, str]: """Allow to toggle user liquidity for specified subnet. - Arguments: + Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index bc61f660b6..9c1d40882d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3201,26 +3201,27 @@ def add_liquidity( price_low: Balance, price_high: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ Adds liquidity to the specified price range. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. liquidity: The amount of liquidity to be added. price_low: The lower bound of the price tick range. In TAO. price_high: The upper bound of the price tick range. In TAO. - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to - `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -3228,7 +3229,7 @@ def add_liquidity( - False and an error message if the submission fails or the wallet cannot be unlocked. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` - method to enable/disable user liquidity. + method to enable/disable user liquidity. """ return add_liquidity_extrinsic( subtensor=self, @@ -3238,9 +3239,10 @@ def add_liquidity( price_low=price_low, price_high=price_high, hotkey=hotkey, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def add_stake_multiple( diff --git a/migration.md b/migration.md index 85ac84c77b..69da29591f 100644 --- a/migration.md +++ b/migration.md @@ -166,3 +166,4 @@ It must include: Note: `raise_error` parameter is included in the list of parameters and parameters order is standardized. - [x] `.set_children_extrinsic` and `.root_set_pending_childkey_cooldown_extrinsic`. `subtensor.set_children` and `subtensor.root_set_pending_childkey_cooldown` methods. - [x] `.commit_reveal_extrinsic` and `subtensor.set_weights` +- [x] `.add_liquidity_extrinsic` and `subtensor.add_liquidity` diff --git a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py index ae780e6c4b..2d90ceaf0f 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py +++ b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py @@ -43,9 +43,10 @@ async def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, use_nonce=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_liquidity.py b/tests/unit_tests/extrinsics/test_liquidity.py index 7d3942909e..c626ce0b3c 100644 --- a/tests/unit_tests/extrinsics/test_liquidity.py +++ b/tests/unit_tests/extrinsics/test_liquidity.py @@ -41,9 +41,10 @@ def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, use_nonce=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index a267f8c95f..dc37a8670b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3698,8 +3698,9 @@ async def test_add_liquidity(subtensor, fake_wallet, mocker): price_high=Balance.from_tao(130).rao, hotkey=None, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 5f3125a3f5..1d5fd377be 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3958,8 +3958,9 @@ def test_add_liquidity(subtensor, fake_wallet, mocker): price_high=Balance.from_tao(130).rao, hotkey=None, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value From f695bdfd424f7d8a6aa0418e1019084c19f5737a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 16:10:55 -0700 Subject: [PATCH 112/177] `.modify_liquidity_extrinsic` and `subtensor.modify_liquidity` + `.remove_liquidity_extrinsic` and `subtensor.remove_liquidity` + `.toggle_user_liquidity_extrinsic` and `subtensor.toggle_user_liquidity` --- bittensor/core/async_subtensor.py | 61 +++++++++++-------- .../core/extrinsics/asyncex/liquidity.py | 57 +++++++++-------- bittensor/core/extrinsics/liquidity.py | 59 ++++++++++-------- bittensor/core/subtensor.py | 55 +++++++++-------- migration.md | 3 + .../extrinsics/asyncex/test_liquidity.py | 9 ++- tests/unit_tests/extrinsics/test_liquidity.py | 9 ++- tests/unit_tests/test_async_subtensor.py | 9 ++- tests/unit_tests/test_subtensor.py | 9 ++- 9 files changed, 159 insertions(+), 112 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 9cc096419d..f224bb7e22 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4585,24 +4585,25 @@ async def modify_liquidity( position_id: int, liquidity_delta: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Modifies liquidity in liquidity position by adding or removing liquidity from it. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to - `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -4636,7 +4637,7 @@ async def modify_liquidity( ) Note: Modifying is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` - to enable/disable user liquidity. + to enable/disable user liquidity. """ return await modify_liquidity_extrinsic( subtensor=self, @@ -4645,9 +4646,10 @@ async def modify_liquidity( position_id=position_id, liquidity_delta=liquidity_delta, hotkey=hotkey, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def move_stake( @@ -4798,23 +4800,24 @@ async def remove_liquidity( netuid: int, position_id: int, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Remove liquidity and credit balances back to wallet's hotkey stake. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to - `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -4823,7 +4826,7 @@ async def remove_liquidity( Note: - Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` - extrinsic to enable/disable user liquidity. + extrinsic to enable/disable user liquidity. - To get the `position_id` use `get_liquidity_list` method. """ return await remove_liquidity_extrinsic( @@ -4832,9 +4835,10 @@ async def remove_liquidity( netuid=netuid, position_id=position_id, hotkey=hotkey, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def reveal_weights( @@ -5437,21 +5441,23 @@ async def toggle_user_liquidity( wallet: "Wallet", netuid: int, enable: bool, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Allow to toggle user liquidity for specified subnet. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -5465,9 +5471,10 @@ async def toggle_user_liquidity( wallet=wallet, netuid=netuid, enable=enable, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def transfer( diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index d9a6fec11b..fc98f46631 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -47,7 +47,7 @@ async def add_liquidity_extrinsic( - False and an error message if the submission fails or the wallet cannot be unlocked. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call - `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. + `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) @@ -86,9 +86,10 @@ async def modify_liquidity_extrinsic( position_id: int, liquidity_delta: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Modifies liquidity in liquidity position by adding or removing liquidity from it. @@ -99,11 +100,12 @@ async def modify_liquidity_extrinsic( position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -135,6 +137,7 @@ async def modify_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, + raise_error=raise_error, ) @@ -144,9 +147,10 @@ async def remove_liquidity_extrinsic( netuid: int, position_id: int, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Remove liquidity and credit balances back to wallet's hotkey stake. @@ -156,19 +160,20 @@ async def remove_liquidity_extrinsic( netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: - True and a success message if the extrinsic is successfully submitted or processed. - False and an error message if the submission fails or the wallet cannot be unlocked. - Note: Adding is allowed even when user liquidity is enabled in specified subnet. - Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. + Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call + `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) @@ -191,6 +196,7 @@ async def remove_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, + raise_error=raise_error, ) @@ -199,9 +205,10 @@ async def toggle_user_liquidity_extrinsic( wallet: "Wallet", netuid: int, enable: bool, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Allow to toggle user liquidity for specified subnet. @@ -210,11 +217,12 @@ async def toggle_user_liquidity_extrinsic( wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -237,4 +245,5 @@ async def toggle_user_liquidity_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index 62a0d0e0e9..dbb974082a 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -47,7 +47,7 @@ def add_liquidity_extrinsic( - False and an error message if the submission fails or the wallet cannot be unlocked. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call - `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. + `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) @@ -86,9 +86,10 @@ def modify_liquidity_extrinsic( position_id: int, liquidity_delta: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Modifies liquidity in liquidity position by adding or removing liquidity from it. @@ -99,11 +100,12 @@ def modify_liquidity_extrinsic( position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -111,7 +113,7 @@ def modify_liquidity_extrinsic( - False and an error message if the submission fails or the wallet cannot be unlocked. Note: Modifying is allowed even when user liquidity is enabled in specified subnet. Call - `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. + `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) @@ -135,6 +137,7 @@ def modify_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, + raise_error=raise_error, ) @@ -144,9 +147,10 @@ def remove_liquidity_extrinsic( netuid: int, position_id: int, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Remove liquidity and credit balances back to wallet's hotkey stake. @@ -156,19 +160,20 @@ def remove_liquidity_extrinsic( netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: - True and a success message if the extrinsic is successfully submitted or processed. - False and an error message if the submission fails or the wallet cannot be unlocked. - Note: Adding is allowed even when user liquidity is enabled in specified subnet. - Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. + Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call + `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) @@ -191,6 +196,7 @@ def remove_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, + raise_error=raise_error, ) @@ -199,9 +205,10 @@ def toggle_user_liquidity_extrinsic( wallet: "Wallet", netuid: int, enable: bool, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Allow to toggle user liquidity for specified subnet. @@ -210,11 +217,12 @@ def toggle_user_liquidity_extrinsic( wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -237,4 +245,5 @@ def toggle_user_liquidity_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 9c1d40882d..8929859462 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3420,24 +3420,25 @@ def modify_liquidity( position_id: int, liquidity_delta: Balance, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Modifies liquidity in liquidity position by adding or removing liquidity from it. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. liquidity_delta: The amount of liquidity to be added or removed (add if positive or remove if negative). - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to - `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -3471,7 +3472,7 @@ def modify_liquidity( ) Note: Modifying is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` - to enable/disable user liquidity. + to enable/disable user liquidity. """ return modify_liquidity_extrinsic( subtensor=self, @@ -3480,9 +3481,10 @@ def modify_liquidity( position_id=position_id, liquidity_delta=liquidity_delta, hotkey=hotkey, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def move_stake( @@ -3634,23 +3636,24 @@ def remove_liquidity( netuid: int, position_id: int, hotkey: Optional[str] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Remove liquidity and credit balances back to wallet's hotkey stake. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. position_id: The id of the position record in the pool. - hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. Defaults to - `None`. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. + hotkey: The hotkey with staked TAO in Alpha. If not passed then the wallet hotkey is used. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -3659,7 +3662,7 @@ def remove_liquidity( Note: - Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` - extrinsic to enable/disable user liquidity. + extrinsic to enable/disable user liquidity. - To get the `position_id` use `get_liquidity_list` method. """ return remove_liquidity_extrinsic( @@ -3668,9 +3671,10 @@ def remove_liquidity( netuid=netuid, position_id=position_id, hotkey=hotkey, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def reveal_weights( @@ -4244,21 +4248,23 @@ def toggle_user_liquidity( wallet: "Wallet", netuid: int, enable: bool, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Allow to toggle user liquidity for specified subnet. - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. enable: Boolean indicating whether to enable user liquidity. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to False. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -4272,9 +4278,10 @@ def toggle_user_liquidity( wallet=wallet, netuid=netuid, enable=enable, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def transfer( diff --git a/migration.md b/migration.md index 69da29591f..a071676a01 100644 --- a/migration.md +++ b/migration.md @@ -167,3 +167,6 @@ Note: `raise_error` parameter is included in the list of parameters and paramete - [x] `.set_children_extrinsic` and `.root_set_pending_childkey_cooldown_extrinsic`. `subtensor.set_children` and `subtensor.root_set_pending_childkey_cooldown` methods. - [x] `.commit_reveal_extrinsic` and `subtensor.set_weights` - [x] `.add_liquidity_extrinsic` and `subtensor.add_liquidity` +- [x] `.modify_liquidity_extrinsic` and `subtensor.modify_liquidity` +- [x] `.remove_liquidity_extrinsic` and `subtensor.remove_liquidity` +- [x] `.toggle_user_liquidity_extrinsic` and `subtensor.toggle_user_liquidity` diff --git a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py index 2d90ceaf0f..c572a518a9 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py +++ b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py @@ -88,9 +88,10 @@ async def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, use_nonce=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -129,9 +130,10 @@ async def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, use_nonce=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -169,7 +171,8 @@ async def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_liquidity.py b/tests/unit_tests/extrinsics/test_liquidity.py index c626ce0b3c..ce60b34bd3 100644 --- a/tests/unit_tests/extrinsics/test_liquidity.py +++ b/tests/unit_tests/extrinsics/test_liquidity.py @@ -85,9 +85,10 @@ def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, use_nonce=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -125,9 +126,10 @@ def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, use_nonce=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -164,7 +166,8 @@ def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index dc37a8670b..a14b8afa72 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3732,8 +3732,9 @@ async def test_modify_liquidity(subtensor, fake_wallet, mocker): liquidity_delta=Balance.from_tao(150), hotkey=None, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value @@ -3763,8 +3764,9 @@ async def test_remove_liquidity(subtensor, fake_wallet, mocker): position_id=position_id, hotkey=None, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value @@ -3793,8 +3795,9 @@ async def test_toggle_user_liquidity(subtensor, fake_wallet, mocker): netuid=netuid, enable=enable, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 1d5fd377be..f5565caf7b 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3991,8 +3991,9 @@ def test_modify_liquidity(subtensor, fake_wallet, mocker): liquidity_delta=Balance.from_tao(150), hotkey=None, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value @@ -4021,8 +4022,9 @@ def test_remove_liquidity(subtensor, fake_wallet, mocker): position_id=position_id, hotkey=None, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value @@ -4050,8 +4052,9 @@ def test_toggle_user_liquidity(subtensor, fake_wallet, mocker): netuid=netuid, enable=enable, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value From 6993732b3e1a52de30d72c561539d7158fe24bfb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 17:13:00 -0700 Subject: [PATCH 113/177] `.transfer_stake_extrinsic` and `subtensor.transfer_stake` --- bittensor/core/async_subtensor.py | 21 ++++--- .../core/extrinsics/asyncex/move_stake.py | 49 +++++++++------- bittensor/core/extrinsics/move_stake.py | 57 ++++++++++--------- bittensor/core/subtensor.py | 35 ++++++------ migration.md | 11 +++- tests/unit_tests/test_subtensor_extended.py | 3 +- 6 files changed, 101 insertions(+), 75 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f224bb7e22..2507b740cb 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5527,25 +5527,27 @@ async def transfer_stake( origin_netuid: int, destination_netuid: int, amount: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Transfers stake from one subnet to another while changing the coldkey owner. - Arguments: + Parameters: wallet: The wallet to transfer stake from. destination_coldkey_ss58: The destination coldkey SS58 address. hotkey_ss58: The hotkey SS58 address associated with the stake. origin_netuid: The source subnet UID. destination_netuid: The destination subnet UID. amount: Amount to transfer. - wait_for_inclusion: If true, waits for inclusion before returning. - wait_for_finalization: If true, waits for finalization before returning. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: success: True if the transfer was successful. @@ -5559,9 +5561,10 @@ async def transfer_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def unstake( diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index d52f86f868..e72b6dda40 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -45,26 +45,28 @@ async def transfer_stake_extrinsic( origin_netuid: int, destination_netuid: int, amount: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Transfers stake from one coldkey to another in the Bittensor network. - Args: - subtensor (AsyncSubtensor): The subtensor instance to interact with the blockchain. - wallet (Wallet): The wallet containing the coldkey to authorize the transfer. - destination_coldkey_ss58 (str): SS58 address of the destination coldkey. - hotkey_ss58 (str): SS58 address of the hotkey associated with the stake. - origin_netuid (int): Network UID of the origin subnet. - destination_netuid (int): Network UID of the destination subnet. - amount (Balance): The amount of stake to transfer as a `Balance` object. - wait_for_inclusion (bool): If True, waits for transaction inclusion in a block. Defaults to `True`. - wait_for_finalization (bool): If True, waits for transaction finalization. Defaults to `False`. - 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. + Parameters: + subtensor: The subtensor instance to interact with the blockchain. + wallet: The wallet containing the coldkey to authorize the transfer. + destination_coldkey_ss58: SS58 address of the destination coldkey. + hotkey_ss58: SS58 address of the hotkey associated with the stake. + origin_netuid: Network UID of the origin subnet. + destination_netuid: Network UID of the destination subnet. + amount: The amount of stake to transfer as a `Balance` object. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: bool: True if the transfer was successful, False otherwise. @@ -108,12 +110,13 @@ async def transfer_stake_extrinsic( }, ) - success, err_msg = await subtensor.sign_and_send_extrinsic( + success, message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: @@ -141,7 +144,7 @@ async def transfer_stake_extrinsic( return True else: - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") + logging.error(f":cross_mark: [red]Failed[/red]: {message}") return False except Exception as e: @@ -156,12 +159,13 @@ async def swap_stake_extrinsic( origin_netuid: int, destination_netuid: int, amount: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Swaps stake from one subnet to another for a given hotkey in the Bittensor network. @@ -302,10 +306,11 @@ async def move_stake_extrinsic( destination_hotkey: str, destination_netuid: int, amount: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, move_all_stake: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Moves stake from one hotkey to another within subnets in the Bittensor network. diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index f3aae363ce..29067c7c70 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -41,27 +41,29 @@ def transfer_stake_extrinsic( hotkey_ss58: str, origin_netuid: int, destination_netuid: int, - amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, + amount: Balance, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Transfers stake from one subnet to another while changing the coldkey owner. - Args: - subtensor (Subtensor): Subtensor instance. - wallet (bittensor.wallet): The wallet to transfer stake from. - destination_coldkey_ss58 (str): The destination coldkey SS58 address. - hotkey_ss58 (str): The hotkey SS58 address associated with the stake. - origin_netuid (int): The source subnet UID. - destination_netuid (int): The destination subnet UID. - amount (Union[Balance, float, int]): Amount to transfer. - wait_for_inclusion (bool): If true, waits for inclusion before returning. - wait_for_finalization (bool): If true, waits for finalization before returning. - 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. + Parameters: + subtensor: The subtensor instance to interact with the blockchain. + wallet: The wallet containing the coldkey to authorize the transfer. + destination_coldkey_ss58: SS58 address of the destination coldkey. + hotkey_ss58: SS58 address of the hotkey associated with the stake. + origin_netuid: Network UID of the origin subnet. + destination_netuid: Network UID of the destination subnet. + amount: The amount of stake to transfer as a `Balance` object. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: success (bool): True if the transfer was successful. @@ -105,12 +107,13 @@ def transfer_stake_extrinsic( }, ) - success, err_msg = subtensor.sign_and_send_extrinsic( + success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: @@ -121,7 +124,7 @@ def transfer_stake_extrinsic( # Get updated stakes origin_stake, dest_stake = _get_stake_in_origin_and_dest( - subtensor, + subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, origin_netuid=origin_netuid, @@ -138,7 +141,7 @@ def transfer_stake_extrinsic( return True else: - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") + logging.error(f":cross_mark: [red]Failed[/red]: {message}") return False except Exception as e: @@ -152,13 +155,14 @@ def swap_stake_extrinsic( hotkey_ss58: str, origin_netuid: int, destination_netuid: int, - amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, + amount: Balance, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. @@ -297,11 +301,12 @@ def move_stake_extrinsic( origin_netuid: int, destination_hotkey: str, destination_netuid: int, - amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, + amount: Balance, move_all_stake: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Moves stake to a different hotkey and/or subnet while keeping the same coldkey owner. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 8929859462..d29d20fe8f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4336,28 +4336,30 @@ def transfer_stake( origin_netuid: int, destination_netuid: int, amount: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Transfers stake from one subnet to another while changing the coldkey owner. - Args: - wallet (bittensor.wallet): The wallet to transfer stake from. - destination_coldkey_ss58 (str): The destination coldkey SS58 address. - hotkey_ss58 (str): The hotkey SS58 address associated with the stake. - origin_netuid (int): The source subnet UID. - destination_netuid (int): The destination subnet UID. - amount (Union[Balance, float, int]): Amount to transfer. - wait_for_inclusion (bool): If true, waits for inclusion before returning. - wait_for_finalization (bool): If true, waits for finalization before returning. - 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. + Parameters: + wallet: The wallet to transfer stake from. + destination_coldkey_ss58: The destination coldkey SS58 address. + hotkey_ss58: The hotkey SS58 address associated with the stake. + origin_netuid: The source subnet UID. + destination_netuid: The destination subnet UID. + amount: Amount to transfer. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - success (bool): True if the transfer was successful. + success: True if the transfer was successful. """ amount = check_and_convert_to_balance(amount) return transfer_stake_extrinsic( @@ -4368,9 +4370,10 @@ def transfer_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def unstake( diff --git a/migration.md b/migration.md index a071676a01..8c7c1cd459 100644 --- a/migration.md +++ b/migration.md @@ -163,10 +163,19 @@ It must include: - [x] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. # Standardize parameter order is applied for (extrinsics and related calls): -Note: `raise_error` parameter is included in the list of parameters and parameters order is standardized. + +These parameters will now exist in all extrinsics and related calls (default values could be different depends by extrinsic): + +```py +period: Optional[int] = None, +raise_error: bool = False, +wait_for_inclusion: bool = False, +wait_for_finalization: bool = False, +``` - [x] `.set_children_extrinsic` and `.root_set_pending_childkey_cooldown_extrinsic`. `subtensor.set_children` and `subtensor.root_set_pending_childkey_cooldown` methods. - [x] `.commit_reveal_extrinsic` and `subtensor.set_weights` - [x] `.add_liquidity_extrinsic` and `subtensor.add_liquidity` - [x] `.modify_liquidity_extrinsic` and `subtensor.modify_liquidity` - [x] `.remove_liquidity_extrinsic` and `subtensor.remove_liquidity` - [x] `.toggle_user_liquidity_extrinsic` and `subtensor.toggle_user_liquidity` +- [x] `.transfer_stake_extrinsic` and `subtensor.transfer_stake` diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 1a53e4e7ba..162d0af8d7 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -15,6 +15,7 @@ from bittensor.utils import U16_MAX, U64_MAX from bittensor.utils.balance import Balance from tests.helpers.helpers import assert_submit_signed_extrinsic +from bittensor.core.extrinsics import move_stake @pytest.fixture @@ -1436,7 +1437,7 @@ def test_transfer_stake_error( "destination_netuid": 1, "alpha_amount": 1, }, - wait_for_finalization=False, + wait_for_finalization=True, wait_for_inclusion=True, ) From 7005ada04214e405e54f15ba0425e597141c1b6f Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 17:35:46 -0700 Subject: [PATCH 114/177] `.swap_stake_extrinsic` and `subtensor.swap_stake` --- bittensor/core/async_subtensor.py | 26 +++++----- .../core/extrinsics/asyncex/move_stake.py | 35 +++++++------ bittensor/core/extrinsics/move_stake.py | 33 +++++++------ bittensor/core/subtensor.py | 49 ++++++++++--------- migration.md | 1 + tests/unit_tests/test_subtensor.py | 2 + tests/unit_tests/test_subtensor_extended.py | 2 +- 7 files changed, 80 insertions(+), 68 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 2507b740cb..5695d180b8 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5379,36 +5379,37 @@ async def swap_stake( origin_netuid: int, destination_netuid: int, amount: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. Like subnet hopping - same owner, same hotkey, just changing which subnet the stake is in. - Arguments: + Parameters: wallet: The wallet to swap stake from. hotkey_ss58: The SS58 address of the hotkey whose stake is being swapped. origin_netuid: The netuid from which stake is removed. destination_netuid: The netuid to which stake is added. amount: The amount to swap. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - safe_staking: If true, enables price safety checks to protect against fluctuating prices. The swap will only - execute if the price ratio between subnets doesn't exceed the rate tolerance. Default is False. + safe_staking: If true, enables price safety checks to protect against fluctuating prices. The swap + will only execute if the price ratio between subnets doesn't exceed the rate tolerance. allow_partial_stake: If true and safe_staking is enabled, allows partial stake swaps when the full amount - would exceed the price threshold. If false, the entire swap fails if it would exceed the threshold. - Default is False. + would exceed the price tolerance. If false, the entire swap fails if it would exceed the tolerance. rate_tolerance: The maximum allowed increase in the price ratio between subnets (origin_price/destination_price). For example, 0.005 = 0.5% maximum increase. Only used when - safe_staking is True. Default is 0.005. + safe_staking is True. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: success: True if the extrinsic was successful. @@ -5428,12 +5429,13 @@ async def swap_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) async def toggle_user_liquidity( diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index e72b6dda40..68edea571b 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -170,24 +170,26 @@ async def swap_stake_extrinsic( """ Swaps stake from one subnet to another for a given hotkey in the Bittensor network. - Args: - subtensor (AsyncSubtensor): The subtensor instance to interact with the blockchain. - wallet (Wallet): The wallet containing the coldkey to authorize the swap. - hotkey_ss58 (str): SS58 address of the hotkey associated with the stake. - origin_netuid (int): Network UID of the origin subnet. - destination_netuid (int): Network UID of the destination subnet. - amount (Balance): The amount of stake to swap as a `Balance` object. - wait_for_inclusion (bool): If True, waits for transaction inclusion in a block. Defaults to True. - wait_for_finalization (bool): If True, waits for transaction finalization. Defaults to False. - safe_staking (bool): If true, enables price safety checks to protect against price impact. - allow_partial_stake (bool): If true, allows partial stake swaps when the full amount would exceed the price tolerance. - rate_tolerance (float): Maximum allowed increase in a price ratio (0.005 = 0.5%). - 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. + Parameters: + subtensor: Subtensor instance. + wallet: The wallet to swap stake from. + hotkey_ss58: The hotkey SS58 address associated with the stake. + origin_netuid: The source subnet UID. + destination_netuid: The destination subnet UID. + amount: Amount to swap. + safe_staking: If true, enables price safety checks to protect against price impact. + allow_partial_stake: If true, allows partial stake swaps when the full amount would exceed the price tolerance. + rate_tolerance: Maximum allowed increase in a price ratio (0.005 = 0.5%). + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + Returns: - bool: True if the swap was successful, False otherwise. + success (bool): True if the swap was successful. """ amount.set_unit(netuid=origin_netuid) @@ -258,6 +260,7 @@ async def swap_stake_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/move_stake.py b/bittensor/core/extrinsics/move_stake.py index 29067c7c70..f2037509f8 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -167,21 +167,23 @@ def swap_stake_extrinsic( """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. - Args: - subtensor (Subtensor): Subtensor instance. - wallet (bittensor.wallet): The wallet to swap stake from. - hotkey_ss58 (str): The hotkey SS58 address associated with the stake. - origin_netuid (int): The source subnet UID. - destination_netuid (int): The destination subnet UID. - amount (Union[Balance, float]): Amount to swap. - wait_for_inclusion (bool): If true, waits for inclusion before returning. - wait_for_finalization (bool): If true, waits for finalization before returning. - safe_staking (bool): If true, enables price safety checks to protect against price impact. - allow_partial_stake (bool): If true, allows partial stake swaps when the full amount would exceed the price tolerance. - rate_tolerance (float): Maximum allowed increase in a price ratio (0.005 = 0.5%). - 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. + Parameters: + subtensor: Subtensor instance. + wallet: The wallet to swap stake from. + hotkey_ss58: The hotkey SS58 address associated with the stake. + origin_netuid: The source subnet UID. + destination_netuid: The destination subnet UID. + amount: Amount to swap. + safe_staking: If true, enables price safety checks to protect against price impact. + allow_partial_stake: If true, allows partial stake swaps when the full amount would exceed the price tolerance. + rate_tolerance: Maximum allowed increase in a price ratio (0.005 = 0.5%). + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + Returns: success (bool): True if the swap was successful. @@ -254,6 +256,7 @@ def swap_stake_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 d29d20fe8f..78a48e5939 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4185,40 +4185,40 @@ def swap_stake( origin_netuid: int, destination_netuid: int, amount: Balance, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. Like subnet hopping - same owner, same hotkey, just changing which subnet the stake is in. - Args: - wallet (bittensor.wallet): The wallet to swap stake from. - hotkey_ss58 (str): The SS58 address of the hotkey whose stake is being swapped. - origin_netuid (int): The netuid from which stake is removed. - destination_netuid (int): The netuid to which stake is added. - amount (Union[Balance, float]): The amount to swap. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - safe_staking (bool): If true, enables price safety checks to protect against fluctuating prices. The swap + Parameters: + wallet: The wallet to swap stake from. + hotkey_ss58: The SS58 address of the hotkey whose stake is being swapped. + origin_netuid: The netuid from which stake is removed. + destination_netuid: The netuid to which stake is added. + amount: The amount to swap. + safe_staking: If true, enables price safety checks to protect against fluctuating prices. The swap will only execute if the price ratio between subnets doesn't exceed the rate tolerance. - Default is False. - allow_partial_stake (bool): If true and safe_staking is enabled, allows partial stake swaps when - the full amount would exceed the price tolerance. If false, the entire swap fails if it would - exceed the tolerance. Default is False. - rate_tolerance (float): The maximum allowed increase in the price ratio between subnets - (origin_price/destination_price). For example, 0.005 = 0.5% maximum increase. Only used - when safe_staking is True. Default is 0.005. - 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. + allow_partial_stake: If true and safe_staking is enabled, allows partial stake swaps when the full amount + would exceed the price tolerance. If false, the entire swap fails if it would exceed the tolerance. + rate_tolerance: The maximum allowed increase in the price ratio between subnets + (origin_price/destination_price). For example, 0.005 = 0.5% maximum increase. Only used when + safe_staking is True. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): True if the extrinsic was successful. + success: True if the extrinsic was successful. The price ratio for swap_stake in safe mode is calculated as: origin_subnet_price / destination_subnet_price When safe_staking is enabled, the swap will only execute if: @@ -4235,12 +4235,13 @@ def swap_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) def toggle_user_liquidity( diff --git a/migration.md b/migration.md index 8c7c1cd459..490c6b5d77 100644 --- a/migration.md +++ b/migration.md @@ -179,3 +179,4 @@ wait_for_finalization: bool = False, - [x] `.remove_liquidity_extrinsic` and `subtensor.remove_liquidity` - [x] `.toggle_user_liquidity_extrinsic` and `subtensor.toggle_user_liquidity` - [x] `.transfer_stake_extrinsic` and `subtensor.transfer_stake` +- [x] `.swap_stake_extrinsic` and `subtensor.swap_stake` \ No newline at end of file diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index f5565caf7b..17539a8232 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3056,6 +3056,7 @@ def test_swap_stake_success(mocker, subtensor, fake_wallet): allow_partial_stake=False, rate_tolerance=0.005, period=None, + raise_error=False, ) assert result == mock_swap_stake_extrinsic.return_value @@ -3101,6 +3102,7 @@ def test_swap_stake_with_safe_staking(mocker, subtensor, fake_wallet): allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, period=None, + raise_error=False, ) assert result == mock_swap_stake_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 162d0af8d7..8765e0046f 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1086,7 +1086,7 @@ def test_swap_stake(mock_substrate, subtensor, fake_wallet, mocker): "alpha_amount": 999, }, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, ) From e49d89d1342ebd35fdded4dbfd172c2a38005b85 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 17:55:31 -0700 Subject: [PATCH 115/177] `.move_stake_extrinsic` and `subtensor.move_stake` + renamed parameters --- bittensor/core/async_subtensor.py | 33 ++++++------ .../core/extrinsics/asyncex/move_stake.py | 54 ++++++++++--------- bittensor/core/extrinsics/move_stake.py | 38 ++++++------- bittensor/core/subtensor.py | 47 ++++++++-------- migration.md | 6 ++- tests/unit_tests/test_subtensor_extended.py | 22 ++++---- 6 files changed, 107 insertions(+), 93 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5695d180b8..6a4dffc590 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4655,32 +4655,34 @@ async def modify_liquidity( async def move_stake( self, wallet: "Wallet", - origin_hotkey: str, + origin_hotkey_ss58: str, origin_netuid: int, - destination_hotkey: str, + destination_hotkey_ss58: str, destination_netuid: int, amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, move_all_stake: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Moves stake to a different hotkey and/or subnet. Arguments: wallet: The wallet to move stake from. - origin_hotkey: The SS58 address of the source hotkey. + origin_hotkey_ss58: The SS58 address of the source hotkey. origin_netuid: The netuid of the source subnet. - destination_hotkey: The SS58 address of the destination hotkey. + destination_hotkey_ss58: The SS58 address of the destination hotkey. destination_netuid: The netuid of the destination subnet. amount: Amount of stake to move. + move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + period: 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 a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - period: 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. - move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. Returns: success: True if the stake movement was successful. @@ -4689,15 +4691,16 @@ async def move_stake( return await move_stake_extrinsic( subtensor=self, wallet=wallet, - origin_hotkey=origin_hotkey, + origin_hotkey_ss58=origin_hotkey_ss58, origin_netuid=origin_netuid, - destination_hotkey=destination_hotkey, + destination_hotkey_ss58=destination_hotkey_ss58, destination_netuid=destination_netuid, amount=amount, + move_all_stake=move_all_stake, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, - move_all_stake=move_all_stake, ) async def register( diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 68edea571b..53018613c9 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -304,9 +304,9 @@ async def swap_stake_extrinsic( async def move_stake_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - origin_hotkey: str, + origin_hotkey_ss58: str, origin_netuid: int, - destination_hotkey: str, + destination_hotkey_ss58: str, destination_netuid: int, amount: Balance, move_all_stake: bool = False, @@ -318,23 +318,24 @@ async def move_stake_extrinsic( """ Moves stake from one hotkey to another within subnets in the Bittensor network. - Args: - subtensor: The subtensor instance to interact with the blockchain. - wallet: The wallet containing the coldkey to authorize the move. - origin_hotkey: SS58 address of the origin hotkey associated with the stake. - origin_netuid: Network UID of the origin subnet. - destination_hotkey: SS58 address of the destination hotkey. - destination_netuid: Network UID of the destination subnet. - amount: The amount of stake to move as a `Balance` object. - wait_for_inclusion: If True, waits for transaction inclusion in a block. Defaults to True. - wait_for_finalization: If True, waits for transaction finalization. Defaults to False. + Parameters: + subtensor: Subtensor instance. + wallet: The wallet to move stake from. + origin_hotkey_ss58: The SS58 address of the source hotkey. + origin_netuid: The netuid of the source subnet. + destination_hotkey_ss58: The SS58 address of the destination hotkey. + destination_netuid: The netuid of the destination subnet. + amount: Amount to move. + move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. period: 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. - move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the move was successful, False otherwise. + success: True if the move was successful. Otherwise, False. """ if not amount and not move_all_stake: logging.error( @@ -345,19 +346,19 @@ async def move_stake_extrinsic( # Check sufficient stake stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey, - destination_hotkey_ss58=destination_hotkey, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_hotkey_ss58=destination_hotkey_ss58, origin_netuid=origin_netuid, destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) if move_all_stake: amount = stake_in_origin elif stake_in_origin < amount: logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {origin_hotkey}. " + f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {origin_hotkey_ss58}. " f"Stake: {stake_in_origin}, amount: {amount}" ) return False @@ -366,7 +367,7 @@ async def move_stake_extrinsic( try: logging.info( - f"Moving stake from hotkey [blue]{origin_hotkey}[/blue] to hotkey [blue]{destination_hotkey}[/blue]\n" + f"Moving stake from hotkey [blue]{origin_hotkey_ss58}[/blue] to hotkey [blue]{destination_hotkey_ss58}[/blue]\n" f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " f"[yellow]{destination_netuid}[/yellow]" ) @@ -374,20 +375,21 @@ async def move_stake_extrinsic( call_module="SubtensorModule", call_function="move_stake", call_params={ - "origin_hotkey": origin_hotkey, + "origin_hotkey": origin_hotkey_ss58, "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey, + "destination_hotkey": destination_hotkey_ss58, "destination_netuid": destination_netuid, "alpha_amount": amount.rao, }, ) - success, err_msg = await subtensor.sign_and_send_extrinsic( + success, message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: @@ -399,8 +401,8 @@ async def move_stake_extrinsic( # Get updated stakes origin_stake, dest_stake = await _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey, - destination_hotkey_ss58=destination_hotkey, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_hotkey_ss58=destination_hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, origin_netuid=origin_netuid, @@ -415,7 +417,7 @@ async def move_stake_extrinsic( return True else: - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") + logging.error(f":cross_mark: [red]Failed[/red]: {message}") return False except Exception as e: diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index f2037509f8..cbef3c8117 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -300,9 +300,9 @@ def swap_stake_extrinsic( def move_stake_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - origin_hotkey: str, + origin_hotkey_ss58: str, origin_netuid: int, - destination_hotkey: str, + destination_hotkey_ss58: str, destination_netuid: int, amount: Balance, move_all_stake: bool = False, @@ -314,20 +314,21 @@ def move_stake_extrinsic( """ Moves stake to a different hotkey and/or subnet while keeping the same coldkey owner. - Args: + Parameters: subtensor: Subtensor instance. wallet: The wallet to move stake from. - origin_hotkey: The SS58 address of the source hotkey. + origin_hotkey_ss58: The SS58 address of the source hotkey. origin_netuid: The netuid of the source subnet. - destination_hotkey: The SS58 address of the destination hotkey. + destination_hotkey_ss58: The SS58 address of the destination hotkey. destination_netuid: The netuid of the destination subnet. amount: Amount to move. - wait_for_inclusion: If true, waits for inclusion before returning. - wait_for_finalization: If true, waits for finalization before returning. + move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. period: 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. - move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: success: True if the move was successful. Otherwise, False. @@ -341,8 +342,8 @@ def move_stake_extrinsic( # Check sufficient stake stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey, - destination_hotkey_ss58=destination_hotkey, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_hotkey_ss58=destination_hotkey_ss58, origin_netuid=origin_netuid, destination_netuid=destination_netuid, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, @@ -353,7 +354,7 @@ def move_stake_extrinsic( elif stake_in_origin < amount: logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {origin_hotkey}. " + f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {origin_hotkey_ss58}. " f"Stake: {stake_in_origin}, amount: {amount}" ) return False @@ -362,27 +363,28 @@ def move_stake_extrinsic( try: logging.info( - f"Moving stake from hotkey [blue]{origin_hotkey}[/blue] to hotkey [blue]{destination_hotkey}[/blue]\n" + f"Moving stake from hotkey [blue]{origin_hotkey_ss58}[/blue] to hotkey [blue]{destination_hotkey_ss58}[/blue]\n" f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid [yellow]{destination_netuid}[/yellow]" ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="move_stake", call_params={ - "origin_hotkey": origin_hotkey, + "origin_hotkey": origin_hotkey_ss58, "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey, + "destination_hotkey": destination_hotkey_ss58, "destination_netuid": destination_netuid, "alpha_amount": amount.rao, }, ) - success, err_msg = subtensor.sign_and_send_extrinsic( + success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: @@ -394,8 +396,8 @@ def move_stake_extrinsic( # Get updated stakes origin_stake, dest_stake = _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey, - destination_hotkey_ss58=destination_hotkey, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_hotkey_ss58=destination_hotkey_ss58, origin_netuid=origin_netuid, destination_netuid=destination_netuid, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, @@ -410,7 +412,7 @@ def move_stake_extrinsic( return True else: - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") + logging.error(f":cross_mark: [red]Failed[/red]: {message}") return False except Exception as e: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 78a48e5939..ea75da65f8 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3490,49 +3490,52 @@ def modify_liquidity( def move_stake( self, wallet: "Wallet", - origin_hotkey: str, + origin_hotkey_ss58: str, origin_netuid: int, - destination_hotkey: str, + destination_hotkey_ss58: str, destination_netuid: int, amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, move_all_stake: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Moves stake to a different hotkey and/or subnet. - Args: - wallet (bittensor.wallet): The wallet to move stake from. - origin_hotkey (str): The SS58 address of the source hotkey. - origin_netuid (int): The netuid of the source subnet. - destination_hotkey (str): The SS58 address of the destination hotkey. - destination_netuid (int): The netuid of the destination subnet. - amount (Balance): Amount of stake to move. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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. + Parameters: + wallet: The wallet to move stake from. + origin_hotkey_ss58: The SS58 address of the source hotkey. + origin_netuid: The netuid of the source subnet. + destination_hotkey_ss58: The SS58 address of the destination hotkey. + destination_netuid: The netuid of the destination subnet. + amount: Amount of stake to move. move_all_stake: If true, moves all stake from the source hotkey to the destination hotkey. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - success (bool): True if the stake movement was successful. + success: True if the stake movement was successful. """ amount = check_and_convert_to_balance(amount) return move_stake_extrinsic( subtensor=self, wallet=wallet, - origin_hotkey=origin_hotkey, + origin_hotkey_ss58=origin_hotkey_ss58, origin_netuid=origin_netuid, - destination_hotkey=destination_hotkey, + destination_hotkey_ss58=destination_hotkey_ss58, destination_netuid=destination_netuid, amount=amount, + move_all_stake=move_all_stake, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, - move_all_stake=move_all_stake, ) def register( diff --git a/migration.md b/migration.md index 490c6b5d77..12532f857d 100644 --- a/migration.md +++ b/migration.md @@ -179,4 +179,8 @@ wait_for_finalization: bool = False, - [x] `.remove_liquidity_extrinsic` and `subtensor.remove_liquidity` - [x] `.toggle_user_liquidity_extrinsic` and `subtensor.toggle_user_liquidity` - [x] `.transfer_stake_extrinsic` and `subtensor.transfer_stake` -- [x] `.swap_stake_extrinsic` and `subtensor.swap_stake` \ No newline at end of file +- [x] `.swap_stake_extrinsic` and `subtensor.swap_stake` +- [x] `.move_stake_extrinsic` and `subtensor.move_stake` +- [x] `.move_stake_extrinsic` has renamed parameters: + - `origin_hotkey` to `origin_hotkey_ss58` + - `destination_hotkey` to `destination_hotkey_ss58` diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 8765e0046f..ddb93dd529 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -696,10 +696,10 @@ def test_last_drand_round(mock_substrate, subtensor): ) def test_move_stake(mock_substrate, subtensor, fake_wallet, wait): success = subtensor.move_stake( - fake_wallet, - origin_hotkey="origin_hotkey", + wallet=fake_wallet, + origin_hotkey_ss58="origin_hotkey", origin_netuid=1, - destination_hotkey="destination_hotkey", + destination_hotkey_ss58="destination_hotkey", destination_netuid=2, amount=Balance(1), wait_for_finalization=wait, @@ -730,9 +730,9 @@ def test_move_stake_insufficient_stake(mock_substrate, subtensor, fake_wallet, m success = subtensor.move_stake( fake_wallet, - origin_hotkey="origin_hotkey", + origin_hotkey_ss58="origin_hotkey", origin_netuid=1, - destination_hotkey="destination_hotkey", + destination_hotkey_ss58="destination_hotkey", destination_netuid=2, amount=Balance(1), ) @@ -750,9 +750,9 @@ def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): success = subtensor.move_stake( fake_wallet, - origin_hotkey="origin_hotkey", + origin_hotkey_ss58="origin_hotkey", origin_netuid=1, - destination_hotkey="destination_hotkey", + destination_hotkey_ss58="destination_hotkey", destination_netuid=2, amount=Balance(1), ) @@ -771,7 +771,7 @@ def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): "destination_netuid": 2, "alpha_amount": 1, }, - wait_for_finalization=False, + wait_for_finalization=True, wait_for_inclusion=True, ) @@ -781,9 +781,9 @@ def test_move_stake_exception(mock_substrate, subtensor, fake_wallet): success = subtensor.move_stake( fake_wallet, - origin_hotkey="origin_hotkey", + origin_hotkey_ss58="origin_hotkey", origin_netuid=1, - destination_hotkey="destination_hotkey", + destination_hotkey_ss58="destination_hotkey", destination_netuid=2, amount=Balance(1), ) @@ -802,7 +802,7 @@ def test_move_stake_exception(mock_substrate, subtensor, fake_wallet): "destination_netuid": 2, "alpha_amount": 1, }, - wait_for_finalization=False, + wait_for_finalization=True, wait_for_inclusion=True, ) From e546c49d6ae3db5ae4c627750e17eb5977668d85 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:05:28 -0700 Subject: [PATCH 116/177] `.burned_register_extrinsic` and `subtensor.burned_register` --- bittensor/core/async_subtensor.py | 22 ++++++++------- .../core/extrinsics/asyncex/registration.py | 17 +++++------- bittensor/core/extrinsics/registration.py | 17 +++++------- bittensor/core/subtensor.py | 27 ++++++++++--------- migration.md | 1 + tests/unit_tests/test_async_subtensor.py | 4 +-- tests/unit_tests/test_subtensor_extended.py | 4 +-- 7 files changed, 45 insertions(+), 47 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 6a4dffc590..87efece5f3 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4455,26 +4455,27 @@ async def burned_register( self, wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Registers a neuron on the Bittensor network by recycling TAO. This method of registration involves recycling TAO tokens, allowing them to be re-mined by performing work on the network. - Arguments: + Args: wallet: The wallet associated with the neuron to be registered. netuid: The unique identifier of the subnet. - wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to - ``False``. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - period: 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. Returns: - bool: `True` if the registration is successful, False otherwise. + bool: ``True`` if the registration is successful, False otherwise. """ async with self: if netuid == 0: @@ -4490,9 +4491,10 @@ async def burned_register( subtensor=self, wallet=wallet, netuid=netuid, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def commit_weights( diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index 051c1badf8..a97b5e57ee 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -23,29 +23,26 @@ async def burned_register_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """Registers the wallet to chain by recycling TAO. - Args: + Parameters: subtensor: Subtensor instance. wallet: Bittensor wallet object. netuid: The ``netuid`` of the subnet to register on. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns - ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, - or returns ``False`` if the extrinsic fails to be finalized within the timeout. period: 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``True``. + success: True if the extrinsic was successful. Otherwise, False. """ block_hash = await subtensor.substrate.get_chain_head() if not await subtensor.subnet_exists(netuid, block_hash=block_hash): diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 730aeee2e6..e9949e0e2e 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -23,29 +23,26 @@ def burned_register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """Registers the wallet to chain by recycling TAO. - Args: + Parameters: subtensor: Subtensor instance. wallet: Bittensor wallet object. netuid: The ``netuid`` of the subnet to register on. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns - ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, - or returns ``False`` if the extrinsic fails to be finalized within the timeout. period: 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. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``True``. + success: True if the extrinsic was successful. Otherwise, False. """ block = subtensor.get_current_block() if not subtensor.subnet_exists(netuid, block=block): diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ea75da65f8..b824fad069 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3291,24 +3291,24 @@ def burned_register( self, wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Registers a neuron on the Bittensor network by recycling TAO. This method of registration involves recycling - TAO tokens, allowing them to be re-mined by performing work on the network. + TAO tokens, allowing them to be re-mined by performing work on the network. Args: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron to be registered. - netuid (int): The unique identifier of the subnet. - wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. Defaults to - `False`. - wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. - Defaults to `True`. - 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. + wallet: The wallet associated with the neuron to be registered. + netuid: The unique identifier of the subnet. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: bool: ``True`` if the registration is successful, False otherwise. @@ -3327,9 +3327,10 @@ def burned_register( subtensor=self, wallet=wallet, netuid=netuid, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def commit_weights( diff --git a/migration.md b/migration.md index 12532f857d..953d33d41b 100644 --- a/migration.md +++ b/migration.md @@ -184,3 +184,4 @@ wait_for_finalization: bool = False, - [x] `.move_stake_extrinsic` has renamed parameters: - `origin_hotkey` to `origin_hotkey_ss58` - `destination_hotkey` to `destination_hotkey_ss58` +- [x] `.burned_register_extrinsic` and `subtensor.burned_register` \ No newline at end of file diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index a14b8afa72..8c3603b285 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -203,7 +203,7 @@ async def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): "hotkey": fake_wallet.hotkey.ss58_address, }, wait_for_finalization=True, - wait_for_inclusion=False, + wait_for_inclusion=True, ) @@ -244,7 +244,7 @@ async def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, m "hotkey": fake_wallet.hotkey.ss58_address, }, wait_for_finalization=True, - wait_for_inclusion=False, + wait_for_inclusion=True, ) diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index ddb93dd529..ba535d1a3e 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -200,7 +200,7 @@ def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): "hotkey": fake_wallet.hotkey.ss58_address, }, wait_for_finalization=True, - wait_for_inclusion=False, + wait_for_inclusion=True, ) @@ -237,7 +237,7 @@ def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker) "hotkey": fake_wallet.hotkey.ss58_address, }, wait_for_finalization=True, - wait_for_inclusion=False, + wait_for_inclusion=True, ) From bdbd1ba778e06f20afed269c36efa7e2ad1657b5 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:08:56 -0700 Subject: [PATCH 117/177] fix `test_move_stake_*` e2e tests --- tests/e2e_tests/test_staking.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index f9f86adc5b..08527ba8a7 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -1383,9 +1383,9 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert subtensor.staking.move_stake( wallet=alice_wallet, - origin_hotkey=alice_wallet.hotkey.ss58_address, + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=alice_subnet_netuid, - destination_hotkey=bob_wallet.hotkey.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, destination_netuid=bob_subnet_netuid, amount=stakes[0].stake, wait_for_finalization=True, @@ -1461,9 +1461,9 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert subtensor.staking.move_stake( wallet=dave_wallet, - origin_hotkey=dave_wallet.hotkey.ss58_address, + origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, origin_netuid=bob_subnet_netuid, - destination_hotkey=bob_wallet.hotkey.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, destination_netuid=bob_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, @@ -1554,9 +1554,9 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ assert await async_subtensor.staking.move_stake( wallet=alice_wallet, - origin_hotkey=alice_wallet.hotkey.ss58_address, + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=alice_subnet_netuid, - destination_hotkey=bob_wallet.hotkey.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, destination_netuid=bob_subnet_netuid, amount=stakes[0].stake, wait_for_finalization=True, @@ -1635,9 +1635,9 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ assert await async_subtensor.staking.move_stake( wallet=dave_wallet, - origin_hotkey=dave_wallet.hotkey.ss58_address, + origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, origin_netuid=bob_subnet_netuid, - destination_hotkey=bob_wallet.hotkey.ss58_address, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, destination_netuid=bob_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, From 97a677a60c193d1f8810ced374764fa62ad57174 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:12:21 -0700 Subject: [PATCH 118/177] docstring refactoring --- bittensor/core/async_subtensor.py | 5 +++++ bittensor/core/subtensor.py | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 87efece5f3..60bca89c7e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4422,6 +4422,7 @@ async def add_stake_multiple( amounts: Optional[list[Balance]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + period: Optional[int] = None, ) -> bool: """ Adds stakes to multiple neurons identified by their hotkey SS58 addresses. @@ -4434,6 +4435,9 @@ async def add_stake_multiple( amounts: Corresponding amounts of TAO to stake for each hotkey. wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `True`. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to `False`. + period: 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. Returns: bool: ``True`` if the staking is successful for all specified neurons, ``False`` otherwise. @@ -4449,6 +4453,7 @@ async def add_stake_multiple( amounts=amounts, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + period=period, ) async def burned_register( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b824fad069..6e5370d4af 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3259,14 +3259,14 @@ def add_stake_multiple( Adds stakes to multiple neurons identified by their hotkey SS58 addresses. This bulk operation allows for efficient staking across different neurons from a single wallet. - Args: - wallet (bittensor_wallet.Wallet): The wallet used for staking. - hotkey_ss58s (list[str]): List of ``SS58`` addresses of hotkeys to stake to. - netuids (list[int]): List of network UIDs to stake to. - amounts (list[Balance]): Corresponding amounts of TAO to stake for each hotkey. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + Parameters: + wallet: The wallet used for staking. + hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. + netuids: List of network UIDs to stake to. + amounts: Corresponding amounts of TAO to stake for each hotkey. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. + period: 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. From 31cce625510c22658514eacc14dbf638253e91d1 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:22:15 -0700 Subject: [PATCH 119/177] `.register_subnet_extrinsic` and `subtensor.register_subnet` --- bittensor/core/async_subtensor.py | 21 +++++++++-------- .../core/extrinsics/asyncex/registration.py | 23 +++++++++++-------- bittensor/core/extrinsics/registration.py | 23 +++++++++++-------- bittensor/core/subtensor.py | 23 ++++++++++--------- migration.md | 3 ++- 5 files changed, 51 insertions(+), 42 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 60bca89c7e..aa407e8e86 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4776,22 +4776,22 @@ async def register( async def register_subnet( self: "AsyncSubtensor", wallet: "Wallet", + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> bool: """ Registers a new subnetwork on the Bittensor network. - Arguments: + Parameters: wallet: The wallet to be used for subnet registration. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, - os `False` if the extrinsic fails to enter the block within the timeout. Default is `False`. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning - true, or returns false if the extrinsic fails to be finalized within the timeout. Default is `False`. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: bool: True if the subnet registration was successful, False otherwise. @@ -4799,9 +4799,10 @@ async def register_subnet( return await register_subnet_extrinsic( subtensor=self, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def remove_liquidity( diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index a97b5e57ee..f50b4d0e22 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -136,21 +136,23 @@ async def burned_register_extrinsic( async def register_subnet_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Registers a new subnetwork on the Bittensor blockchain asynchronously. - Args: - subtensor (AsyncSubtensor): The async subtensor interface to send the extrinsic. - wallet (Wallet): The wallet to be used for subnet registration. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning true. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning true. - 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. + Parameters: + subtensor: The subtensor interface to send the extrinsic. + wallet: The wallet to be used for subnet registration. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: bool: True if the subnet registration was successful, False otherwise. @@ -179,6 +181,7 @@ async def register_subnet_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index e9949e0e2e..82f7314cb4 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -128,21 +128,23 @@ def burned_register_extrinsic( def register_subnet_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Registers a new subnetwork on the Bittensor blockchain. - Args: - subtensor (Subtensor): The subtensor interface to send the extrinsic. - wallet (Wallet): The wallet to be used for subnet registration. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning true. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning true. - 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. + Parameters: + subtensor: The subtensor interface to send the extrinsic. + wallet: The wallet to be used for subnet registration. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: bool: True if the subnet registration was successful, False otherwise. @@ -171,6 +173,7 @@ def register_subnet_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 6e5370d4af..10bde6e8be 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3606,22 +3606,22 @@ def register( def register_subnet( self, wallet: "Wallet", + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> bool: """ Registers a new subnetwork on the Bittensor network. - Args: - wallet (bittensor_wallet.Wallet): The wallet to be used for subnet registration. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or - returns `False` if the extrinsic fails to enter the block within the timeout. Default is `False`. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. Default is `True`. - 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. + Parameters: + wallet: The wallet to be used for subnet registration. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: bool: True if the subnet registration was successful, False otherwise. @@ -3629,9 +3629,10 @@ def register_subnet( return register_subnet_extrinsic( subtensor=self, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def remove_liquidity( diff --git a/migration.md b/migration.md index 953d33d41b..766b8adbfd 100644 --- a/migration.md +++ b/migration.md @@ -184,4 +184,5 @@ wait_for_finalization: bool = False, - [x] `.move_stake_extrinsic` has renamed parameters: - `origin_hotkey` to `origin_hotkey_ss58` - `destination_hotkey` to `destination_hotkey_ss58` -- [x] `.burned_register_extrinsic` and `subtensor.burned_register` \ No newline at end of file +- [x] `.burned_register_extrinsic` and `subtensor.burned_register` +- [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` \ No newline at end of file From 22e0c0a14fcdf66dd082dd3e78a70d320cb8f1aa Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:31:35 -0700 Subject: [PATCH 120/177] fix registration subnets in e2e tests --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/subtensor.py | 2 +- tests/e2e_tests/test_commitment.py | 2 +- tests/e2e_tests/test_delegate.py | 14 +++----- tests/e2e_tests/test_dendrite.py | 10 +++--- tests/e2e_tests/test_hotkeys.py | 8 ++--- tests/e2e_tests/test_incentive.py | 6 ++-- tests/e2e_tests/test_metagraph.py | 10 +++--- tests/e2e_tests/test_reveal_commitments.py | 4 +-- tests/e2e_tests/test_staking.py | 40 ++++++++++----------- tests/e2e_tests/test_subnets.py | 12 ++----- tests/e2e_tests/test_subtensor_functions.py | 4 +-- 12 files changed, 48 insertions(+), 66 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index aa407e8e86..e3ef9279ec 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4778,7 +4778,7 @@ async def register_subnet( wallet: "Wallet", period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 10bde6e8be..f495cd8199 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3608,7 +3608,7 @@ def register_subnet( wallet: "Wallet", period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index 860601f075..d16c9a7104 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -16,7 +16,7 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): dave_subnet_netuid = 2 - assert subtensor.subnets.register_subnet(dave_wallet, True, True) + assert subtensor.subnets.register_subnet(dave_wallet) assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( "Subnet wasn't created successfully" ) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index a63bfaab39..1b5c136c04 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -663,11 +663,7 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet( - alice_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ), "Subnet wasn't created" + assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -752,11 +748,9 @@ async def test_nominator_min_required_stake_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" + 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), ( diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index 841e349557..c4a8085444 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -35,9 +35,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( - "Subnet wasn't created." - ) + assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created." # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -168,9 +166,9 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall 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" + 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), ( diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 47c2b94ce6..f91d876da6 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -34,7 +34,7 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): 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.register_subnet(dave_wallet) assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( f"Subnet #{dave_subnet_netuid} does not exist." ) @@ -90,7 +90,7 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): 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.register_subnet(dave_wallet) assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( f"Subnet #{dave_subnet_netuid} does not exist." ) @@ -165,7 +165,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): == "Success with `root_set_pending_childkey_cooldown_extrinsic` response." ) - assert subtensor.subnets.register_subnet(dave_wallet, True, True) + assert subtensor.subnets.register_subnet(dave_wallet) assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( f"Subnet #{dave_subnet_netuid} does not exist." ) @@ -490,7 +490,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa == "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.register_subnet(dave_wallet) assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( f"Subnet #{dave_subnet_netuid} does not exist." ) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 70cda9b48b..0cee9beb8f 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -35,9 +35,7 @@ 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), "Subnet wasn't created" # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -208,7 +206,7 @@ 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), ( + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( "Subnet wasn't created" ) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index fba4e6fea7..6f7279abc4 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -56,7 +56,7 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 logging.console.info("Register the subnet through Alice") - assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( + assert subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" ) @@ -375,7 +375,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): logging.console.info("Testing [blue]test_metagraph_info[/blue]") alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.register_subnet(alice_wallet) metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=1, block=1) @@ -635,7 +635,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): 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) + assert await async_subtensor.subnets.register_subnet(alice_wallet) metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( netuid=1, block=1 @@ -900,7 +900,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): logging.console.info("Testing [blue]test_metagraph_info_with_indexes[/blue]") alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.register_subnet(alice_wallet) field_indices = [ SelectiveMetagraphIndex.Name, @@ -1136,7 +1136,7 @@ async def test_metagraph_info_with_indexes_async( 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) + assert await async_subtensor.subnets.register_subnet(alice_wallet) field_indices = [ SelectiveMetagraphIndex.Name, diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index 97b60373fd..840d3cdf1d 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -38,7 +38,7 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): logging.console.info("Testing Drand encrypted commitments.") # Register subnet as Alice - assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( + assert subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" ) @@ -152,7 +152,7 @@ async def test_set_reveal_commitment(async_subtensor, alice_wallet, bob_wallet): logging.console.info("Testing Drand encrypted commitments.") # Register subnet as Alice - assert await async_subtensor.subnets.register_subnet(alice_wallet, True, True), ( + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" ) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 08527ba8a7..c51272ef00 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -29,7 +29,7 @@ def test_single_operation(subtensor, 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) + assert subtensor.subnets.register_subnet(alice_wallet) # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -205,7 +205,7 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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) + assert await async_subtensor.subnets.register_subnet(alice_wallet) # Verify subnet created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -694,7 +694,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) 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) + assert subtensor.extrinsics.register_subnet(alice_wallet) # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -893,7 +893,7 @@ async def test_safe_staking_scenarios_async( 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) + assert await async_subtensor.extrinsics.register_subnet(alice_wallet) # Verify subnet created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -1093,12 +1093,12 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): # Create new subnet (netuid 2) and register Alice origin_netuid = 2 - assert subtensor.subnets.register_subnet(bob_wallet, True, True) + assert subtensor.subnets.register_subnet(bob_wallet) 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.register_subnet(bob_wallet) assert subtensor.subnets.subnet_exists(dest_netuid), ( "Subnet wasn't created successfully" ) @@ -1213,12 +1213,12 @@ async def test_safe_swap_stake_scenarios_async( # 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.register_subnet(bob_wallet) 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.register_subnet(bob_wallet) assert await async_subtensor.subnets.subnet_exists(dest_netuid), ( "Subnet wasn't created successfully" ) @@ -1328,7 +1328,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): 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.register_subnet(alice_wallet) assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1360,7 +1360,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ] bob_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - subtensor.subnets.register_subnet(bob_wallet, True, True) + subtensor.subnets.register_subnet(bob_wallet) assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1493,7 +1493,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ logging.console.info("Testing [blue]test_move_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.register_subnet(alice_wallet) assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1529,7 +1529,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ ] bob_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 - await async_subtensor.subnets.register_subnet(bob_wallet, True, True) + await async_subtensor.subnets.register_subnet(bob_wallet) assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1666,7 +1666,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.register_subnet(alice_wallet) assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1715,7 +1715,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert bob_stakes == [] dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - subtensor.subnets.register_subnet(dave_wallet, True, True) + subtensor.subnets.register_subnet(dave_wallet) assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) @@ -1799,7 +1799,7 @@ async def test_transfer_stake_async( 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.register_subnet(alice_wallet) assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1850,7 +1850,7 @@ async def test_transfer_stake_async( assert bob_stakes == [] dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 - await async_subtensor.subnets.register_subnet(dave_wallet, True, True) + await async_subtensor.subnets.register_subnet(dave_wallet) assert await async_wait_to_start_call( async_subtensor, dave_wallet, dave_subnet_netuid @@ -1942,7 +1942,7 @@ def test_unstaking_with_limit( # Register first SN alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.register_subnet(alice_wallet) assert subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( "Subnet wasn't created successfully" ) @@ -1966,7 +1966,7 @@ def test_unstaking_with_limit( # Register second SN alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(alice_wallet, True, True) + assert subtensor.subnets.register_subnet(alice_wallet) assert subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( "Subnet wasn't created successfully" ) @@ -2070,7 +2070,7 @@ async def test_unstaking_with_limit_async( # 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.register_subnet(alice_wallet) assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( "Subnet wasn't created successfully" ) @@ -2096,7 +2096,7 @@ async def test_unstaking_with_limit_async( # 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.register_subnet(alice_wallet) assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( "Subnet wasn't created successfully" ) diff --git a/tests/e2e_tests/test_subnets.py b/tests/e2e_tests/test_subnets.py index 8b28206fdc..89028d2d06 100644 --- a/tests/e2e_tests/test_subnets.py +++ b/tests/e2e_tests/test_subnets.py @@ -14,11 +14,7 @@ def test_subnets(subtensor, alice_wallet): subnets = subtensor.subnets.all_subnets() assert len(subnets) == 2 - subtensor.subnets.register_subnet( - alice_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + subtensor.subnets.register_subnet(alice_wallet) subnets = subtensor.subnets.all_subnets() assert len(subnets) == 3 @@ -50,11 +46,7 @@ async def test_subnets_async(async_subtensor, alice_wallet): 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, - ) + assert await async_subtensor.subnets.register_subnet(alice_wallet) subnets = await async_subtensor.subnets.all_subnets() assert len(subnets) == 3 diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 11342caec5..669b929d3f 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -68,7 +68,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall pre_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() # Register subnet - assert subtensor.subnets.register_subnet(alice_wallet, True, True), ( + assert subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" ) @@ -259,7 +259,7 @@ async def test_subtensor_extrinsics_async( 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), ( + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" ) From 8b0ae8986c3dbf69481f46025766071cff256cc0 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:48:25 -0700 Subject: [PATCH 121/177] fix registration subnets in e2e tests --- bittensor/core/subtensor.py | 4 ++-- tests/helpers/helpers.py | 2 +- tests/unit_tests/test_subtensor_extended.py | 12 +++++------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f495cd8199..25be51f2ed 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3543,7 +3543,7 @@ def register( self, wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, max_allowed_attempts: int = 3, output_in_place: bool = True, @@ -3752,7 +3752,7 @@ def reveal_weights( def root_register( self, wallet: "Wallet", - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, period: Optional[int] = None, ) -> bool: diff --git a/tests/helpers/helpers.py b/tests/helpers/helpers.py index eccf716811..6f94196273 100644 --- a/tests/helpers/helpers.py +++ b/tests/helpers/helpers.py @@ -84,7 +84,7 @@ def assert_submit_signed_extrinsic( call_params: Optional[dict] = None, era: Optional[dict] = None, nonce: Optional[int] = None, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ): substrate.compose_call.assert_called_with( diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index ba535d1a3e..1f0449cc19 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1206,15 +1206,13 @@ def test_register_subnet(mock_substrate, subtensor, fake_wallet, mocker, success is_success=success, ) - result = subtensor.register_subnet( - fake_wallet, - ) + result = subtensor.register_subnet(fake_wallet) assert result is success assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, + substrate=mock_substrate, + keypair=fake_wallet.coldkey, call_module="SubtensorModule", call_function="register_network", call_params={ @@ -1264,8 +1262,8 @@ def test_root_register(mock_substrate, subtensor, fake_wallet, mocker): ) assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, + substrate=mock_substrate, + keypair=fake_wallet.coldkey, call_module="SubtensorModule", call_function="root_register", call_params={ From f792c4e5cf35ef1038023aab2879bc6aadd50ff9 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:04:33 -0700 Subject: [PATCH 122/177] `.register_extrinsic` and `subtensor.register` --- bittensor/core/async_subtensor.py | 45 ++++++++-------- .../core/extrinsics/asyncex/registration.py | 44 +++++++-------- bittensor/core/extrinsics/registration.py | 40 +++++++------- bittensor/core/subtensor.py | 54 ++++++++++--------- migration.md | 3 +- .../extrinsics/asyncex/test_registration.py | 3 ++ tests/unit_tests/test_async_subtensor.py | 7 +-- 7 files changed, 104 insertions(+), 92 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e3ef9279ec..bd2a098299 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4714,8 +4714,6 @@ async def register( self: "AsyncSubtensor", wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, max_allowed_attempts: int = 3, output_in_place: bool = False, cuda: bool = False, @@ -4725,33 +4723,37 @@ async def register( update_interval: Optional[int] = None, log_verbose: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ): """ - Registers a neuron on the Bittensor network using the provided wallet. + Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. Registration is a critical step for a neuron to become an active participant in the network, enabling it to stake, set weights, and receive incentives. - Arguments: + Parameters: wallet: The wallet associated with the neuron to be registered. - netuid: unique identifier of the subnet. - wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to + netuid: The unique identifier of the subnet. max_allowed_attempts: Maximum number of attempts to register the wallet. - output_in_place: If true, prints the progress of the proof of work to the console in-place. Meaning - the progress is printed on the same lines. Defaults to `True`. - cuda: If `true`, the wallet should be registered using CUDA device(s). Defaults to `False`. - dev_id: The CUDA device id to use, or a list of device ids. Defaults to `0` (zero). - tpb: The number of threads per block (CUDA). Default to `256`. - num_processes: The number of processes to use to register. Default to `None`. - update_interval: The number of nonces to solve between updates. Default to `None`. - log_verbose: If `true`, the registration process will log more information. Default to `False`. - period: 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. + output_in_place: If true, prints the progress of the proof of work to the console in-place. Meaning the + progress is printed on the same lines. + cuda: If ``true``, the wallet should be registered using CUDA device(s). + dev_id: The CUDA device id to use, or a list of device ids. + tpb: The number of threads per block (CUDA). + num_processes: The number of processes to use to register. + update_interval: The number of nonces to solve between updates. + log_verbose: If ``true``, the registration process will log more information. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: `True` if the registration is successful, False otherwise. + bool: ``True`` if the registration is successful, False otherwise. This function facilitates the entry of new neurons into the network, supporting the decentralized growth and scalability of the Bittensor ecosystem. @@ -4760,8 +4762,6 @@ async def register( subtensor=self, wallet=wallet, netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, max_allowed_attempts=max_allowed_attempts, tpb=tpb, update_interval=update_interval, @@ -4771,6 +4771,9 @@ async def register( output_in_place=output_in_place, log_verbose=log_verbose, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) async def register_subnet( diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index f50b4d0e22..bbb1afa1d7 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -201,8 +201,6 @@ async def register_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, max_allowed_attempts: int = 3, output_in_place: bool = True, cuda: bool = False, @@ -212,33 +210,36 @@ async def register_extrinsic( update_interval: Optional[int] = None, log_verbose: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Registers the wallet to the chain. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): initialized AsyncSubtensor object to use for chain - interactions - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - netuid (int): The ``netuid`` of the subnet to register on. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - max_allowed_attempts (int): Maximum number of attempts to register the wallet. - output_in_place (bool): Whether the POW solving should be outputted to the console as it goes along. - cuda (bool): If `True`, the wallet should be registered using CUDA device(s). + """Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. + + Registration is a critical step for a neuron to become an active participant in the network, enabling it to stake, + set weights, and receive incentives. + + Parameters: + subtensor: Subtensor object to use for chain interactions + wallet: Bittensor wallet object. + netuid: The ``netuid`` of the subnet to register on. + max_allowed_attempts: Maximum number of attempts to register the wallet. + output_in_place: Whether the POW solving should be outputted to the console as it goes along. + cuda: If `True`, the wallet should be registered using CUDA device(s). dev_id: The CUDA device id to use, or a list of device ids. tpb: The number of threads per block (CUDA). num_processes: The number of processes to use to register. update_interval: The number of nonces to solve between updates. log_verbose: If `True`, the registration process will log more information. - 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, the - response is `True`. + bool: True if the subnet registration was successful, False otherwise. """ block_hash = await subtensor.substrate.get_chain_head() logging.debug("[magenta]Checking subnet status... [/magenta]") @@ -343,6 +344,7 @@ async def register_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not success: diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 82f7314cb4..4d9af329ee 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -193,8 +193,6 @@ def register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, max_allowed_attempts: int = 3, output_in_place: bool = True, cuda: bool = False, @@ -204,32 +202,33 @@ def register_extrinsic( update_interval: Optional[int] = None, log_verbose: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Registers the wallet to the chain. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor object to use for chain interactions - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - netuid (int): The ``netuid`` of the subnet to register on. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - max_allowed_attempts (int): Maximum number of attempts to register the wallet. - output_in_place (bool): Whether the POW solving should be outputted to the console as it goes along. - cuda (bool): If `True`, the wallet should be registered using CUDA device(s). + """Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. + + Parameters: + subtensor: Subtensor object to use for chain interactions + wallet: Bittensor wallet object. + netuid: The ``netuid`` of the subnet to register on. + max_allowed_attempts: Maximum number of attempts to register the wallet. + output_in_place: Whether the POW solving should be outputted to the console as it goes along. + cuda: If `True`, the wallet should be registered using CUDA device(s). dev_id: The CUDA device id to use, or a list of device ids. tpb: The number of threads per block (CUDA). num_processes: The number of processes to use to register. update_interval: The number of nonces to solve between updates. log_verbose: If `True`, the registration process will log more information. - 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, the - response is `True`. + bool: True if the subnet registration was successful, False otherwise. """ logging.debug("[magenta]Checking subnet status... [/magenta]") @@ -339,6 +338,7 @@ def register_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not success: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 25be51f2ed..d0286c5639 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3543,8 +3543,6 @@ def register( self, wallet: "Wallet", netuid: int, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, max_allowed_attempts: int = 3, output_in_place: bool = True, cuda: bool = False, @@ -3554,44 +3552,45 @@ def register( update_interval: Optional[int] = None, log_verbose: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ - Registers a neuron on the Bittensor network using the provided wallet. + Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. Registration is a critical step for a neuron to become an active participant in the network, enabling it to - stake, set weights, and receive incentives. + stake, set weights, and receive incentives. - Args: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron to be registered. - netuid (int): The unique identifier of the subnet. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. Defaults to `False`. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Defaults to - `True`. - max_allowed_attempts (int): Maximum number of attempts to register the wallet. - output_in_place (bool): If true, prints the progress of the proof of work to the console in-place. Meaning - the progress is printed on the same lines. Defaults to `True`. - cuda (bool): If ``true``, the wallet should be registered using CUDA device(s). Defaults to `False`. - dev_id (Union[List[int], int]): The CUDA device id to use, or a list of device ids. Defaults to `0` (zero). - tpb (int): The number of threads per block (CUDA). Default to `256`. - num_processes (Optional[int]): The number of processes to use to register. Default to `None`. - update_interval (Optional[int]): The number of nonces to solve between updates. Default to `None`. - log_verbose (bool): If ``true``, the registration process will log more information. Default to `False`. - 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. + Parameters: + wallet: The wallet associated with the neuron to be registered. + netuid: The unique identifier of the subnet. + max_allowed_attempts: Maximum number of attempts to register the wallet. + output_in_place: If true, prints the progress of the proof of work to the console in-place. Meaning the + progress is printed on the same lines. + cuda: If ``true``, the wallet should be registered using CUDA device(s). + dev_id: The CUDA device id to use, or a list of device ids. + tpb: The number of threads per block (CUDA). + num_processes: The number of processes to use to register. + update_interval: The number of nonces to solve between updates. + log_verbose: If ``true``, the registration process will log more information. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: bool: ``True`` if the registration is successful, False otherwise. - This function facilitates the entry of new neurons into the network, supporting the decentralized - growth and scalability of the Bittensor ecosystem. + This function facilitates the entry of new neurons into the network, supporting the decentralized growth and + scalability of the Bittensor ecosystem. """ return register_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, max_allowed_attempts=max_allowed_attempts, tpb=tpb, update_interval=update_interval, @@ -3601,6 +3600,9 @@ def register( output_in_place=output_in_place, log_verbose=log_verbose, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) def register_subnet( diff --git a/migration.md b/migration.md index 766b8adbfd..7fdf0bea04 100644 --- a/migration.md +++ b/migration.md @@ -185,4 +185,5 @@ wait_for_finalization: bool = False, - `origin_hotkey` to `origin_hotkey_ss58` - `destination_hotkey` to `destination_hotkey_ss58` - [x] `.burned_register_extrinsic` and `subtensor.burned_register` -- [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` \ No newline at end of file +- [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` +- [x] `.register_extrinsic` and `subtensor.register` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index 181394a8bb..df4608390a 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -59,6 +59,7 @@ async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" @@ -124,6 +125,7 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, mock wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" @@ -281,6 +283,7 @@ async def is_stale_side_effect(*_, **__): wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result is False diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 8c3603b285..b45a4b6e2c 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2624,6 +2624,7 @@ async def test_register_success(subtensor, fake_wallet, mocker): # Asserts mocked_register_extrinsic.assert_awaited_once_with( + wallet=fake_wallet, cuda=False, dev_id=0, log_verbose=False, @@ -2634,10 +2635,10 @@ async def test_register_success(subtensor, fake_wallet, mocker): subtensor=subtensor, tpb=256, update_interval=None, - wait_for_finalization=True, - wait_for_inclusion=False, - wallet=fake_wallet, period=None, + raise_error=False, + wait_for_finalization=True, + wait_for_inclusion=True, ) assert result == mocked_register_extrinsic.return_value From de1607b285589ac53490f05e895497337dee98a4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:11:26 -0700 Subject: [PATCH 123/177] fix tests related with async register --- tests/e2e_tests/test_metagraph.py | 18 +++++------------- tests/e2e_tests/test_set_weights.py | 16 ++++++---------- tests/e2e_tests/test_staking.py | 12 ++---------- tests/unit_tests/test_subtensor_extended.py | 4 +--- 4 files changed, 14 insertions(+), 36 deletions(-) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 6f7279abc4..7fc6c85d8c 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -216,9 +216,9 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w 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" + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) logging.console.info("Verify subnet was created successfully") assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -594,11 +594,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): ] alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet( - alice_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + assert subtensor.subnets.register_subnet(alice_wallet) block = subtensor.chain.get_current_block() metagraph_info = subtensor.metagraphs.get_metagraph_info( @@ -860,11 +856,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): ] 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, - ) + assert await async_subtensor.subnets.register_subnet(alice_wallet) block = await async_subtensor.chain.get_current_block() metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 0e153b804e..4024a34e5d 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -54,11 +54,9 @@ def test_set_weights_uses_next_nonce(subtensor, 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" + assert subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) # Verify all subnets created successfully assert subtensor.subnets.subnet_exists(netuid), ( @@ -222,11 +220,9 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): 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" + assert await async_subtensor.subnets.register_subnet(alice_wallet), ( + "Unable to register the subnet" + ) # Verify all subnets created successfully assert await async_subtensor.subnets.subnet_exists(netuid), ( diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index c51272ef00..3fad46d386 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -396,11 +396,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): ] for _ in netuids: - subtensor.subnets.register_subnet( - alice_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + subtensor.subnets.register_subnet(alice_wallet) # make sure we passed start_call limit for both subnets for netuid in netuids: @@ -547,11 +543,7 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) ] for _ in netuids: - await async_subtensor.subnets.register_subnet( - wallet=alice_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + await async_subtensor.subnets.register_subnet(alice_wallet) # make sure we passed start_call limit for both subnets for netuid in netuids: diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 1f0449cc19..01a365bd31 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1228,9 +1228,7 @@ def test_register_subnet_insufficient_funds( mocker.patch.object(subtensor, "get_balance", return_value=Balance(0)) mocker.patch.object(subtensor, "get_subnet_burn_cost", return_value=Balance(10)) - success = subtensor.register_subnet( - fake_wallet, - ) + success = subtensor.register_subnet(fake_wallet) assert success is False From 3789e644d7adce90b9d953636a6dbd9571ad7655 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:21:06 -0700 Subject: [PATCH 124/177] `.set_subnet_identity_extrinsic` and `subtensor.set_subnet_identity` --- bittensor/core/async_subtensor.py | 26 ++++++----- .../core/extrinsics/asyncex/registration.py | 46 ++++++++++--------- bittensor/core/extrinsics/registration.py | 46 ++++++++++--------- bittensor/core/subtensor.py | 32 +++++++------ migration.md | 3 +- .../extrinsics/asyncex/test_registration.py | 4 +- .../extrinsics/test_registration.py | 6 ++- tests/unit_tests/test_async_subtensor.py | 5 +- tests/unit_tests/test_subtensor.py | 5 +- 9 files changed, 96 insertions(+), 77 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index bd2a098299..8c0a60b1fe 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5142,29 +5142,30 @@ async def set_subnet_identity( wallet: "Wallet", netuid: int, subnet_identity: SubnetIdentity, - wait_for_inclusion: bool = False, + period: Optional[int] = 8, + raise_error: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Sets the identity of a subnet for a specific wallet and network. - Arguments: + Parameters: wallet: The wallet instance that will authorize the transaction. netuid: The unique ID of the network on which the operation takes place. - subnet_identity: The identity data of the subnet including attributes like name, GitHub - repository, contact, URL, discord, description, and any additional metadata. - wait_for_inclusion: Indicates if the function should wait for the transaction to be included in the - block. - wait_for_finalization: Indicates if the function should wait for the transaction to reach - finalization. + subnet_identity: The identity data of the subnet including attributes like name, GitHub repository, contact, + URL, discord, description, and any additional metadata. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ return await set_subnet_identity_extrinsic( subtensor=self, @@ -5178,9 +5179,10 @@ async def set_subnet_identity( discord=subnet_identity.discord, description=subnet_identity.description, additional=subnet_identity.additional, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def set_weights( diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index bbb1afa1d7..2e67368be5 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -407,34 +407,37 @@ async def set_subnet_identity_extrinsic( discord: str, description: str, additional: str, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ Set the identity information for a given subnet. - Arguments: - subtensor (AsyncSubtensor): An instance of the Subtensor class to interact with the blockchain. - wallet (Wallet): A wallet instance used to sign and submit the extrinsic. - netuid (int): The unique ID for the subnet. - subnet_name (str): The name of the subnet to assign the identity information. - github_repo (str): URL of the GitHub repository related to the subnet. - subnet_contact (str): Subnet's contact information, e.g., email or contact link. - subnet_url (str): The URL of the subnet's primary web portal. - logo_url (str): The URL of the logo's primary web portal. - discord (str): Discord server or contact for the subnet. - description (str): A textual description of the subnet. - additional (str): Any additional metadata or information related to the subnet. - wait_for_inclusion (bool): Whether to wait for the extrinsic inclusion in a block (default: False). - wait_for_finalization (bool): Whether to wait for the extrinsic finalization in a block (default: True). - 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. + Parameters: + subtensor: An instance of the Subtensor class to interact with the blockchain. + wallet: A wallet instance used to sign and submit the extrinsic. + netuid: The unique ID for the subnet. + subnet_name: The name of the subnet to assign the identity information. + github_repo: URL of the GitHub repository related to the subnet. + subnet_contact: Subnet's contact information, e.g., email or contact link. + subnet_url: The URL of the subnet's primary web portal. + logo_url: The URL of the logo's primary web portal. + discord: Discord server or contact for the subnet. + description: A textual description of the subnet. + additional: Any additional metadata or information related to the subnet. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: A tuple where the first element indicates success or failure (True/False), and the second - element contains a descriptive message. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ if not (unlock := unlock_key(wallet)).success: @@ -464,6 +467,7 @@ async def set_subnet_identity_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 4d9af329ee..6f667ff7b6 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -401,34 +401,37 @@ def set_subnet_identity_extrinsic( discord: str, description: str, additional: str, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """ Set the identity information for a given subnet. - Arguments: - subtensor (Subtensor): An instance of the Subtensor class to interact with the blockchain. - wallet (Wallet): A wallet instance used to sign and submit the extrinsic. - netuid (int): The unique ID for the subnet. - subnet_name (str): The name of the subnet to assign the identity information. - github_repo (str): URL of the GitHub repository related to the subnet. - subnet_contact (str): Subnet's contact information, e.g., email or contact link. - subnet_url (str): The URL of the subnet's primary web portal. - logo_url (str): The URL of the logo's primary web portal. - discord (str): Discord server or contact for the subnet. - description (str): A textual description of the subnet. - additional (str): Any additional metadata or information related to the subnet. - wait_for_inclusion (bool): Whether to wait for the extrinsic inclusion in a block (default: False). - wait_for_finalization (bool): Whether to wait for the extrinsic finalization in a block (default: True). - 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. + Parameters: + subtensor: An instance of the Subtensor class to interact with the blockchain. + wallet: A wallet instance used to sign and submit the extrinsic. + netuid: The unique ID for the subnet. + subnet_name: The name of the subnet to assign the identity information. + github_repo: URL of the GitHub repository related to the subnet. + subnet_contact: Subnet's contact information, e.g., email or contact link. + subnet_url: The URL of the subnet's primary web portal. + logo_url: The URL of the logo's primary web portal. + discord: Discord server or contact for the subnet. + description: A textual description of the subnet. + additional: Any additional metadata or information related to the subnet. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: A tuple where the first element indicates success or failure (True/False), and the second - element contains a descriptive message. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ if not (unlock := unlock_key(wallet)).success: @@ -458,6 +461,7 @@ def set_subnet_identity_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index d0286c5639..3b3be9f0f9 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3951,29 +3951,30 @@ def set_subnet_identity( wallet: "Wallet", netuid: int, subnet_identity: SubnetIdentity, - wait_for_inclusion: bool = False, + period: Optional[int] = 8, + raise_error: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Sets the identity of a subnet for a specific wallet and network. - Arguments: - wallet (Wallet): The wallet instance that will authorize the transaction. - netuid (int): The unique ID of the network on which the operation takes place. - subnet_identity (SubnetIdentity): The identity data of the subnet including attributes like name, GitHub - repository, contact, URL, discord, description, and any additional metadata. - wait_for_inclusion (bool): Indicates if the function should wait for the transaction to be included in the - block. - wait_for_finalization (bool): Indicates if the function should wait for the transaction to reach - finalization. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + Parameters: + wallet: The wallet instance that will authorize the transaction. + netuid: The unique ID of the network on which the operation takes place. + subnet_identity: The identity data of the subnet including attributes like name, GitHub repository, contact, + URL, discord, description, and any additional metadata. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ return set_subnet_identity_extrinsic( subtensor=self, @@ -3987,9 +3988,10 @@ def set_subnet_identity( discord=subnet_identity.discord, description=subnet_identity.description, additional=subnet_identity.additional, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def set_weights( diff --git a/migration.md b/migration.md index 7fdf0bea04..ac7c2ef09d 100644 --- a/migration.md +++ b/migration.md @@ -186,4 +186,5 @@ wait_for_finalization: bool = False, - `destination_hotkey` to `destination_hotkey_ss58` - [x] `.burned_register_extrinsic` and `subtensor.burned_register` - [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` -- [x] `.register_extrinsic` and `subtensor.register` \ No newline at end of file +- [x] `.register_extrinsic` and `subtensor.register` +- [x] `.set_subnet_identity_extrinsic` and `subtensor.set_subnet_identity` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index df4608390a..03f00aab82 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -345,9 +345,10 @@ async def test_set_subnet_identity_extrinsic_is_success(subtensor, fake_wallet, mocked_sign_and_send_extrinsic.assert_awaited_once_with( call=mocked_compose_call.return_value, wallet=fake_wallet, - wait_for_inclusion=False, + wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result == (True, "Identities for subnet 123 are set.") @@ -416,6 +417,7 @@ async def test_set_subnet_identity_extrinsic_is_failed(subtensor, fake_wallet, m wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result == ( diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index 97b0fb785f..796c3d45f0 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -270,9 +270,10 @@ def test_set_subnet_identity_extrinsic_is_success(mock_subtensor, mock_wallet, m mocked_sign_and_send_extrinsic.assert_called_once_with( call=mocked_compose_call.return_value, wallet=mock_wallet, - wait_for_inclusion=False, + wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result == (True, "Identities for subnet 123 are set.") @@ -335,9 +336,10 @@ def test_set_subnet_identity_extrinsic_is_failed(mock_subtensor, mock_wallet, mo mocked_sign_and_send_extrinsic.assert_called_once_with( call=mocked_compose_call.return_value, wallet=mock_wallet, - wait_for_inclusion=False, + wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result == ( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index b45a4b6e2c..19c0024961 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3003,9 +3003,10 @@ async def test_set_subnet_identity(mocker, subtensor, fake_wallet): discord=fake_subnet_identity.discord, description=fake_subnet_identity.description, additional=fake_subnet_identity.additional, + period=8, + raise_error=False, wait_for_finalization=True, - wait_for_inclusion=False, - period=None, + wait_for_inclusion=True, ) assert result == mocked_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 17539a8232..20c8f151d9 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3245,9 +3245,10 @@ def test_set_subnet_identity(mocker, subtensor, fake_wallet): discord=fake_subnet_identity.discord, description=fake_subnet_identity.description, additional=fake_subnet_identity.additional, + period=8, + raise_error=False, wait_for_finalization=True, - wait_for_inclusion=False, - period=None, + wait_for_inclusion=True, ) assert result == mocked_extrinsic.return_value From db123b6a49c1e8389c1c2c8bc607068fbceb213b Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:28:29 -0700 Subject: [PATCH 125/177] update `subtensor.subnets.burned_register` call --- tests/e2e_tests/test_commitment.py | 6 +- tests/e2e_tests/test_delegate.py | 8 --- tests/e2e_tests/test_hotkeys.py | 6 +- tests/e2e_tests/test_metagraph.py | 12 +--- tests/e2e_tests/test_reveal_commitments.py | 5 +- tests/e2e_tests/test_staking.py | 64 ++-------------------- 6 files changed, 17 insertions(+), 84 deletions(-) diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index d16c9a7104..d31bf739ef 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -31,12 +31,12 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): ) assert subtensor.subnets.burned_register( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, ) uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( - alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -110,7 +110,7 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): ) assert await sub.subnets.burned_register( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, ) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 1b5c136c04..1a36389b57 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -677,15 +677,11 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ 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, ) success = subtensor.staking.add_stake( @@ -766,15 +762,11 @@ async def test_nominator_min_required_stake_async( 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( diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index f91d876da6..f9fde7a37f 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -60,8 +60,8 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): is False ) - subtensor.subnets.burned_register( - alice_wallet, + assert subtensor.subnets.burned_register( + wallet=alice_wallet, netuid=dave_subnet_netuid, ) @@ -119,7 +119,7 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): ) assert await async_subtensor.subnets.burned_register( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, ) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 7fc6c85d8c..570eefc460 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -560,8 +560,6 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): 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) @@ -820,8 +818,6 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): assert await async_subtensor.subnets.burned_register( bob_wallet, netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( @@ -996,10 +992,8 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) assert subtensor.subnets.burned_register( - bob_wallet, + wallet=bob_wallet, netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) fields = [ @@ -1234,10 +1228,8 @@ async def test_metagraph_info_with_indexes_async( ) assert await async_subtensor.subnets.burned_register( - bob_wallet, + wallet=bob_wallet, netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) fields = [ diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index 840d3cdf1d..9ad12eaa76 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -46,7 +46,8 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): # Register Bob's neuron assert subtensor.subnets.burned_register( - bob_wallet, alice_subnet_netuid, True, True + wallet=bob_wallet, + netuid=alice_subnet_netuid, ), "Bob's neuron was not register." # Verify subnet 2 created successfully @@ -162,7 +163,7 @@ async def test_set_reveal_commitment(async_subtensor, alice_wallet, bob_wallet): # Register Bob's neuron assert await async_subtensor.subnets.burned_register( - bob_wallet, alice_subnet_netuid, True, True + bob_wallet, alice_subnet_netuid ), "Bob's neuron was not register." # Verify subnet 2 created successfully diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 3fad46d386..27b3468c89 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -41,15 +41,11 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): 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.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}") @@ -219,15 +215,11 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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}") @@ -404,10 +396,8 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): for netuid in netuids: subtensor.subnets.burned_register( - bob_wallet, - netuid, - wait_for_inclusion=True, - wait_for_finalization=True, + wallet=bob_wallet, + netuid=netuid, ) for netuid in netuids: @@ -553,8 +543,6 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) await async_subtensor.subnets.burned_register( wallet=bob_wallet, netuid=netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) for netuid in netuids: @@ -1103,14 +1091,10 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): 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 @@ -1223,14 +1207,10 @@ async def test_safe_swap_stake_scenarios_async( 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 @@ -1362,15 +1342,11 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): 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( @@ -1533,15 +1509,11 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ 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, ) assert await async_subtensor.staking.move_stake( @@ -1666,10 +1638,8 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) subtensor.subnets.burned_register( - alice_wallet, + wallet=alice_wallet, netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert subtensor.staking.add_stake( @@ -1712,10 +1682,8 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) subtensor.subnets.burned_register( - bob_wallet, + wallet=bob_wallet, netuid=dave_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert subtensor.staking.transfer_stake( @@ -1801,10 +1769,8 @@ async def test_transfer_stake_async( ) await async_subtensor.subnets.burned_register( - alice_wallet, + wallet=alice_wallet, netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert await async_subtensor.staking.add_stake( @@ -1849,10 +1815,8 @@ async def test_transfer_stake_async( ) await async_subtensor.subnets.burned_register( - bob_wallet, + wallet=bob_wallet, netuid=dave_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert await async_subtensor.staking.transfer_stake( @@ -1945,15 +1909,11 @@ def test_unstaking_with_limit( assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid_2, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid_2, - wait_for_inclusion=True, - wait_for_finalization=True, ) # Register second SN @@ -1969,15 +1929,11 @@ def test_unstaking_with_limit( assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid_3, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert 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. @@ -2075,15 +2031,11 @@ async def test_unstaking_with_limit_async( 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 @@ -2099,15 +2051,11 @@ async def test_unstaking_with_limit_async( 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. From db71ccd52354d8119098b309a4eb89718301fda8 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:36:31 -0700 Subject: [PATCH 126/177] oops, period=None --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/subtensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 8c0a60b1fe..e739016207 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5142,7 +5142,7 @@ async def set_subnet_identity( wallet: "Wallet", netuid: int, subnet_identity: SubnetIdentity, - period: Optional[int] = 8, + period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 3b3be9f0f9..e4c99eb31f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3951,7 +3951,7 @@ def set_subnet_identity( wallet: "Wallet", netuid: int, subnet_identity: SubnetIdentity, - period: Optional[int] = 8, + period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, From c6043fed46fc2ad6ced81694027197ebe6497fcb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:39:33 -0700 Subject: [PATCH 127/177] =?UTF-8?q?oops=20again=20-=20need=20to=20take=20a?= =?UTF-8?q?=20rest=20=F0=9F=98=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit_tests/test_async_subtensor.py | 2 +- tests/unit_tests/test_subtensor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 19c0024961..ecc791bfaa 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3003,7 +3003,7 @@ async def test_set_subnet_identity(mocker, subtensor, fake_wallet): discord=fake_subnet_identity.discord, description=fake_subnet_identity.description, additional=fake_subnet_identity.additional, - period=8, + period=None, raise_error=False, wait_for_finalization=True, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 20c8f151d9..9c8103f1a2 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3245,7 +3245,7 @@ def test_set_subnet_identity(mocker, subtensor, fake_wallet): discord=fake_subnet_identity.discord, description=fake_subnet_identity.description, additional=fake_subnet_identity.additional, - period=8, + period=None, raise_error=False, wait_for_finalization=True, wait_for_inclusion=True, From dcfd78968a029c396ee59da0e77687df9c9f9386 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 21:21:43 -0700 Subject: [PATCH 128/177] `.root_register_extrinsic`, `subtensor.burned_register` and `subtensor.root_register` --- bittensor/core/async_subtensor.py | 31 ++++++++++----------- bittensor/core/extrinsics/asyncex/root.py | 27 ++++++++++--------- bittensor/core/extrinsics/root.py | 33 ++++++++++++----------- bittensor/core/subtensor.py | 27 ++++++++++--------- migration.md | 3 ++- tests/unit_tests/extrinsics/test_root.py | 1 + 6 files changed, 65 insertions(+), 57 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e739016207..571e20ad50 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4469,7 +4469,7 @@ async def burned_register( Registers a neuron on the Bittensor network by recycling TAO. This method of registration involves recycling TAO tokens, allowing them to be re-mined by performing work on the network. - Args: + Parameters: wallet: The wallet associated with the neuron to be registered. netuid: The unique identifier of the subnet. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -4487,9 +4487,10 @@ async def burned_register( return await root_register_extrinsic( subtensor=self, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) return await burned_register_extrinsic( @@ -4967,34 +4968,34 @@ async def root_set_pending_childkey_cooldown( async def root_register( self, wallet: "Wallet", - block_hash: Optional[str] = None, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> bool: """ Register neuron by recycling some TAO. - Arguments: - wallet: Bittensor wallet instance. - block_hash: This argument will be removed in Bittensor v10 - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is - `False`. - period: 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. + Parameters: + wallet: The wallet associated with the neuron to be registered. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - `True` if registration was successful, otherwise `False`. + bool: ``True`` if the registration is successful, False otherwise. """ return await root_register_extrinsic( subtensor=self, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def set_children( diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index bab6f0b230..bb318cdb90 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -37,26 +37,26 @@ async def _get_limits(subtensor: "AsyncSubtensor") -> tuple[int, float]: async def root_register_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> bool: - """Registers the wallet to the root network. + """ + Registers the neuron to the root network. Arguments: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The AsyncSubtensor object - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - 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. + subtensor: Subtensor instance to interact with the blockchain. + wallet: Bittensor Wallet instance. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, - the response is `True`. + bool: True if the subnet registration was successful, False otherwise. """ netuid = 0 logging.info( @@ -114,6 +114,7 @@ async def root_register_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not success: diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index 1606bf76da..161ca53fd0 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -38,26 +38,26 @@ def _get_limits(subtensor: "Subtensor") -> tuple[int, float]: def root_register_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Registers the wallet to the root network. - - Arguments: - subtensor (bittensor.core.subtensor.Subtensor): The Subtensor object - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - 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. + """ + Registers the neuron to the root network. + + Parameters: + subtensor: Subtensor instance to interact with the blockchain. + wallet: Bittensor Wallet instance. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - `True` if extrinsic was finalized or included in the block. If we did not wait for finalization/inclusion, the - response is `True`. + bool: True if the subnet registration was successful, False otherwise. """ netuid = 0 logging.info( @@ -113,6 +113,7 @@ def root_register_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not success: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e4c99eb31f..52aea8887e 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3300,7 +3300,7 @@ def burned_register( Registers a neuron on the Bittensor network by recycling TAO. This method of registration involves recycling TAO tokens, allowing them to be re-mined by performing work on the network. - Args: + Parameters: wallet: The wallet associated with the neuron to be registered. netuid: The unique identifier of the subnet. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -3318,9 +3318,10 @@ def burned_register( return root_register_extrinsic( subtensor=self, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) return burned_register_extrinsic( @@ -3754,32 +3755,34 @@ def reveal_weights( def root_register( self, wallet: "Wallet", + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> bool: """ Register neuron by recycling some TAO. - Arguments: + Parameters: wallet (bittensor_wallet.Wallet): Bittensor wallet instance. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``False``. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is - ``False``. - 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - `True` if registration was successful, otherwise `False`. + bool: ``True`` if the registration is successful, False otherwise. """ return root_register_extrinsic( subtensor=self, wallet=wallet, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def root_set_pending_childkey_cooldown( diff --git a/migration.md b/migration.md index ac7c2ef09d..a26722daab 100644 --- a/migration.md +++ b/migration.md @@ -187,4 +187,5 @@ wait_for_finalization: bool = False, - [x] `.burned_register_extrinsic` and `subtensor.burned_register` - [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` - [x] `.register_extrinsic` and `subtensor.register` -- [x] `.set_subnet_identity_extrinsic` and `subtensor.set_subnet_identity` \ No newline at end of file +- [x] `.set_subnet_identity_extrinsic` and `subtensor.set_subnet_identity` +- [x] `.root_register_extrinsic`, `subtensor.burned_register` and `subtensor.root_register` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index 5a16a10909..4a4c11e8e8 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -104,6 +104,7 @@ def test_root_register_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=None, + raise_error=False, ) From 1596818e2d455eeabf8ef8375130b1b494258eeb Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 21:58:10 -0700 Subject: [PATCH 129/177] `.serve_extrinsic`, `.serve_axon_extrinsic` and `subtensor.serve_axon` --- bittensor/core/async_subtensor.py | 33 ++-- bittensor/core/extrinsics/asyncex/serving.py | 100 ++++++------ bittensor/core/extrinsics/serving.py | 151 +++++++------------ bittensor/core/subtensor.py | 35 +++-- migration.md | 4 +- tests/unit_tests/extrinsics/test_serving.py | 106 +++++++------ tests/unit_tests/test_subtensor.py | 133 +--------------- 7 files changed, 219 insertions(+), 343 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 571e20ad50..ae0ec9a4a2 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5319,27 +5319,31 @@ async def serve_axon( self, netuid: int, axon: "Axon", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, certificate: Optional[Certificate] = None, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, ) -> bool: """ - Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. This function is used to - set up the Axon, a key component of a neuron that handles incoming queries and data processing tasks. + Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. - Arguments: + This function is used to set up the Axon, a key component of a neuron that handles incoming queries and data + processing tasks. + + Parameters: netuid: The unique identifier of the subnetwork. axon: The Axon instance to be registered for serving. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is `True`. - certificate: Certificate to use for TLS. If `None`, no TLS will be used. Defaults to `None`. - period: 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. + certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - bool: `True` if the Axon serve registration is successful, False otherwise. + bool: ``True`` if the Axon serve registration is successful, False otherwise. By registering an Axon, the neuron becomes an active part of the network's distributed computing infrastructure, contributing to the collective intelligence of Bittensor. @@ -5348,10 +5352,11 @@ async def serve_axon( subtensor=self, netuid=netuid, axon=axon, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, certificate=certificate, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) async def start_call( diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index ffd952e049..e8084b023f 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -76,35 +76,34 @@ async def serve_extrinsic( netuid: int, placeholder1: int = 0, placeholder2: int = 0, - wait_for_inclusion: bool = False, - wait_for_finalization=True, certificate: Optional[Certificate] = None, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Subscribes a Bittensor endpoint to the subtensor chain. - - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Subtensor instance object. - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - ip (str): Endpoint host port i.e., ``192.122.31.4``. - port (int): Endpoint port number i.e., ``9221``. - protocol (int): An ``int`` representation of the protocol. - netuid (int): The network uid to serve on. - placeholder1 (int): A placeholder for future use. - placeholder2 (int): A placeholder for future use. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. - certificate (bittensor.utils.Certificate): Certificate to use for TLS. If ``None``, no TLS will be used. - Defaults to ``None``. - 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. + """ + Subscribes a Bittensor endpoint to the subtensor chain. + + Parameters: + subtensor: Subtensor instance object. + wallet: Bittensor wallet object. + ip: Endpoint host port i.e., ``192.122.31.4``. + port: Endpoint port number i.e., ``9221``. + protocol: An ``int`` representation of the protocol. + netuid: The network uid to serve on. + placeholder1: A placeholder for future use. + placeholder2: A placeholder for future use. + certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``True``. + bool: True if the subnet registration was successful, False otherwise. """ # Decrypt hotkey if not (unlock := unlock_key(wallet, "hotkey")).success: @@ -141,13 +140,25 @@ async def serve_extrinsic( f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " f"[green]{subtensor.network}:{netuid}[/green]" ) - success, message = await do_serve_axon( - subtensor=subtensor, + + if params.certificate is None: + call_function = "serve_axon" + else: + call_function = "serve_axon_tls" + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=params.dict(), + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - call_params=params, - wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with="hotkey", period=period, + raise_error=raise_error, ) if success: @@ -165,30 +176,30 @@ async def serve_axon_extrinsic( subtensor: "AsyncSubtensor", netuid: int, axon: "Axon", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, certificate: Optional[Certificate] = None, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, ) -> bool: - """Serves the axon to the network. + """ + Serves the axon to the network. - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): Subtensor instance object. + Parameters: + subtensor: AsyncSubtensor instance object. netuid (int): The ``netuid`` being served on. axon (bittensor.core.axon.Axon): Axon to serve. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. certificate (bittensor.utils.Certificate): Certificate to use for TLS. If ``None``, no TLS will be used. Defaults to ``None``. - 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``True``. + bool: True if the subnet registration was successful, False otherwise. """ if not (unlock := unlock_key(axon.wallet, "hotkey")).success: logging.error(unlock.message) @@ -219,10 +230,11 @@ async def serve_axon_extrinsic( port=external_port, protocol=4, netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, certificate=certificate, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) return serve_success diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 09903d763e..6a8a85f00c 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -16,56 +16,6 @@ from bittensor.core.subtensor import Subtensor -def do_serve_axon( - subtensor: "Subtensor", - wallet: "Wallet", - call_params: "AxonServeCallParams", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, - period: Optional[int] = None, -) -> tuple[bool, str]: - """ - Internal method to submit a serve axon transaction to the Bittensor blockchain. This method creates and submits a - transaction, enabling a neuron's ``Axon`` to serve requests on the network. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance object. - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron. - call_params (bittensor.core.types.AxonServeCallParams): Parameters required for the serve axon call. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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. - - Returns: - tuple[bool, Optional[str]]: A tuple containing a success flag and an optional error message. - - This function is crucial for initializing and announcing a neuron's ``Axon`` service on the network, enhancing the - decentralized computation capabilities of Bittensor. - """ - if call_params.certificate is None: - call_function = "serve_axon" - else: - call_function = "serve_axon_tls" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params.dict(), - ) - - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - ) - return success, message - - def serve_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -75,35 +25,34 @@ def serve_extrinsic( netuid: int, placeholder1: int = 0, placeholder2: int = 0, - wait_for_inclusion: bool = False, - wait_for_finalization=True, certificate: Optional[Certificate] = None, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Subscribes a Bittensor endpoint to the subtensor chain. - - Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance object. - wallet (bittensor_wallet.Wallet): Bittensor wallet object. - ip (str): Endpoint host port i.e., ``192.122.31.4``. - port (int): Endpoint port number i.e., ``9221``. - protocol (int): An ``int`` representation of the protocol. - netuid (int): The network uid to serve on. - placeholder1 (int): A placeholder for future use. - placeholder2 (int): A placeholder for future use. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. - certificate (bittensor.utils.Certificate): Certificate to use for TLS. If ``None``, no TLS will be used. - Defaults to ``None``. - 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. + """ + Subscribes a Bittensor endpoint to the subtensor chain. + + Parameters: + subtensor: Subtensor instance object. + wallet: Bittensor wallet object. + ip: Endpoint host port i.e., ``192.122.31.4``. + port: Endpoint port number i.e., ``9221``. + protocol: An ``int`` representation of the protocol. + netuid: The network uid to serve on. + placeholder1: A placeholder for future use. + placeholder2: A placeholder for future use. + certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``True``. + bool: True if the subnet registration was successful, False otherwise. """ # Decrypt hotkey if not (unlock := unlock_key(wallet, "hotkey")).success: @@ -140,13 +89,26 @@ def serve_extrinsic( f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " f"[green]{subtensor.network}:{netuid}[/green]" ) - success, message = do_serve_axon( - subtensor=subtensor, + + if params.certificate is None: + call_function = "serve_axon" + else: + call_function = "serve_axon_tls" + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=params.dict(), + ) + + success, message = subtensor.sign_and_send_extrinsic( + call=call, wallet=wallet, - call_params=params, - wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with="hotkey", period=period, + raise_error=raise_error, ) if success: @@ -164,30 +126,30 @@ def serve_axon_extrinsic( subtensor: "Subtensor", netuid: int, axon: "Axon", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, certificate: Optional["Certificate"] = None, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, ) -> bool: - """Serves the axon to the network. + """ + Serves the axon to the network. - Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance object. + Parameters: + subtensor: Subtensor instance object. netuid (int): The ``netuid`` being served on. axon (bittensor.core.axon.Axon): Axon to serve. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. certificate (bittensor.utils.Certificate): Certificate to use for TLS. If ``None``, no TLS will be used. Defaults to ``None``. - 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is ``True`` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is ``true``. + bool: True if the subnet registration was successful, False otherwise. """ if not (unlock := unlock_key(axon.wallet, "hotkey")).success: logging.error(unlock.message) @@ -216,10 +178,11 @@ def serve_axon_extrinsic( port=external_port, protocol=4, netuid=netuid, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, certificate=certificate, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) return serve_success diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 52aea8887e..b1735c6f38 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4116,41 +4116,44 @@ def serve_axon( self, netuid: int, axon: "Axon", - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, certificate: Optional[Certificate] = None, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = True, ) -> bool: """ - Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. This function is used to - set up the Axon, a key component of a neuron that handles incoming queries and data processing tasks. + Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. - Args: - netuid (int): The unique identifier of the subnetwork. - axon (bittensor.core.axon.Axon): The Axon instance to be registered for serving. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``False``. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is - ``True``. - certificate (bittensor.utils.Certificate): Certificate to use for TLS. If ``None``, no TLS will be used. - Defaults to ``None``. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + This function is used to set up the Axon, a key component of a neuron that handles incoming queries and data + processing tasks. + + Parameters: + netuid: The unique identifier of the subnetwork. + axon: The Axon instance to be registered for serving. + certificate: Certificate to use for TLS. If ``None``, no TLS will be used. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: bool: ``True`` if the Axon serve registration is successful, False otherwise. By registering an Axon, the neuron becomes an active part of the network's distributed computing infrastructure, - contributing to the collective intelligence of Bittensor. + contributing to the collective intelligence of Bittensor. """ return serve_axon_extrinsic( subtensor=self, netuid=netuid, axon=axon, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, certificate=certificate, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) def start_call( diff --git a/migration.md b/migration.md index a26722daab..29d7bd9783 100644 --- a/migration.md +++ b/migration.md @@ -188,4 +188,6 @@ wait_for_finalization: bool = False, - [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` - [x] `.register_extrinsic` and `subtensor.register` - [x] `.set_subnet_identity_extrinsic` and `subtensor.set_subnet_identity` -- [x] `.root_register_extrinsic`, `subtensor.burned_register` and `subtensor.root_register` \ No newline at end of file +- [x] `.root_register_extrinsic`, `subtensor.burned_register` and `subtensor.root_register` +- [x] `.serve_extrinsic` +- [x] `.serve_axon_extrinsic` and `subtensor.serve_axon` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 7095ea0bc1..4fca1b7bc8 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -110,20 +110,22 @@ def test_serve_extrinsic_happy_path( test_id, mocker, ): - # Arrange - serving.do_serve_axon = mocker.MagicMock(return_value=(True, "")) - # Act + # Prep + mocker.patch.object( + mock_subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + # Call result = serving.serve_extrinsic( - mock_subtensor, - mock_wallet, - ip, - port, - protocol, - netuid, - placeholder1, - placeholder2, - wait_for_inclusion, - wait_for_finalization, + subtensor=mock_subtensor, + wallet=mock_wallet, + ip=ip, + port=port, + protocol=protocol, + netuid=netuid, + placeholder1=placeholder1, + placeholder2=placeholder2, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) # Assert @@ -164,20 +166,23 @@ def test_serve_extrinsic_edge_cases( test_id, mocker, ): - # Arrange - serving.do_serve_axon = mocker.MagicMock(return_value=(True, "")) - # Act + # Prep + mocker.patch.object( + mock_subtensor, "sign_and_send_extrinsic", return_value=(True, "") + ) + + # Call result = serving.serve_extrinsic( - mock_subtensor, - mock_wallet, - ip, - port, - protocol, - netuid, - placeholder1, - placeholder2, - wait_for_inclusion, - wait_for_finalization, + subtensor=mock_subtensor, + wallet=mock_wallet, + ip=ip, + port=port, + protocol=protocol, + netuid=netuid, + placeholder1=placeholder1, + placeholder2=placeholder2, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) # Assert @@ -218,20 +223,22 @@ def test_serve_extrinsic_error_cases( test_id, mocker, ): - # Arrange - serving.do_serve_axon = mocker.MagicMock(return_value=(False, "Error serving axon")) - # Act + # Prep + mocker.patch.object( + mock_subtensor, "sign_and_send_extrinsic", return_value=(False, "") + ) + # Call result = serving.serve_extrinsic( - mock_subtensor, - mock_wallet, - ip, - port, - protocol, - netuid, - placeholder1, - placeholder2, - wait_for_inclusion, - wait_for_finalization, + subtensor=mock_subtensor, + wallet=mock_wallet, + ip=ip, + port=port, + protocol=protocol, + netuid=netuid, + placeholder1=placeholder1, + placeholder2=placeholder2, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) # Assert @@ -281,29 +288,32 @@ def test_serve_axon_extrinsic( mocker, ): mock_axon.external_ip = external_ip - # Arrange + # Preps with patch( "bittensor.utils.networking.get_external_ip", side_effect=Exception("Failed to fetch IP") if not external_ip_success else MagicMock(return_value="192.168.1.1"), ): - serving.do_serve_axon = mocker.MagicMock(return_value=(serve_success, "")) - # Act + mocker.patch.object( + mock_subtensor, "sign_and_send_extrinsic", return_value=(serve_success, "") + ) + + # Calls if not external_ip_success: with pytest.raises(ConnectionError): serving.serve_axon_extrinsic( - mock_subtensor, - netuid, - mock_axon, + subtensor=mock_subtensor, + netuid=netuid, + axon=mock_axon, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) else: result = serving.serve_axon_extrinsic( - mock_subtensor, - netuid, - mock_axon, + subtensor=mock_subtensor, + netuid=netuid, + axon=mock_axon, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 9c8103f1a2..d9a1a6d401 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -15,7 +15,6 @@ from bittensor.core.async_subtensor import AsyncSubtensor, logging from bittensor.core.axon import Axon from bittensor.core.chain_data import SubnetHyperparameters, SelectiveMetagraphIndex -from bittensor.core.extrinsics.serving import do_serve_axon from bittensor.core.settings import version_as_int from bittensor.core.subtensor import Subtensor from bittensor.core.types import AxonServeCallParams @@ -1222,7 +1221,10 @@ def test_serve_axon(subtensor, mocker): # Call result = subtensor.serve_axon( - fake_netuid, fake_axon, fake_wait_for_inclusion, fake_wait_for_finalization + netuid=fake_netuid, + axon=fake_axon, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, ) # Asserts @@ -1230,10 +1232,11 @@ def test_serve_axon(subtensor, mocker): subtensor=subtensor, netuid=fake_netuid, axon=fake_axon, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, certificate=fake_certificate, period=None, + raise_error=False, + wait_for_inclusion=fake_wait_for_inclusion, + wait_for_finalization=fake_wait_for_finalization, ) assert result == mocked_serve_axon_extrinsic.return_value @@ -1453,128 +1456,6 @@ def test_neuron_for_uid_success(subtensor, mocker): assert result == mocked_neuron_from_dict.return_value -@pytest.mark.parametrize( - ["fake_call_params", "expected_call_function"], - [ - (call_params(), "serve_axon"), - (call_params_with_certificate(), "serve_axon_tls"), - ], -) -def test_do_serve_axon_is_success( - subtensor, fake_wallet, mocker, fake_call_params, expected_call_function -): - """Successful do_serve_axon call.""" - # Prep - fake_wait_for_inclusion = True - fake_wait_for_finalization = True - - mocker.patch.object(subtensor, "sign_and_send_extrinsic", return_value=(True, "")) - - # Call - result = do_serve_axon( - subtensor=subtensor, - wallet=fake_wallet, - call_params=fake_call_params, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function=expected_call_function, - call_params=fake_call_params, - ) - - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - sign_with="hotkey", - period=None, - ) - - assert result[0] is True - assert result[1] == "" - - -def test_do_serve_axon_is_not_success(subtensor, fake_wallet, mocker, fake_call_params): - """Unsuccessful do_serve_axon call.""" - # Prep - fake_wait_for_inclusion = True - fake_wait_for_finalization = True - - mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(False, None) - ) - - # Call - result = do_serve_axon( - subtensor=subtensor, - wallet=fake_wallet, - call_params=fake_call_params, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="serve_axon", - call_params=fake_call_params, - ) - - subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - sign_with="hotkey", - period=None, - ) - - assert result == (False, None) - - -def test_do_serve_axon_no_waits(subtensor, fake_wallet, mocker, fake_call_params): - """Unsuccessful do_serve_axon call.""" - # Prep - fake_wait_for_inclusion = False - fake_wait_for_finalization = False - - mocked_sign_and_send_extrinsic = mocker.Mock(return_value=(True, "")) - mocker.patch.object( - subtensor, "sign_and_send_extrinsic", new=mocked_sign_and_send_extrinsic - ) - - # Call - result = do_serve_axon( - subtensor=subtensor, - wallet=fake_wallet, - call_params=fake_call_params, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - ) - - # Asserts - subtensor.substrate.compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="serve_axon", - call_params=fake_call_params, - ) - - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, - wallet=fake_wallet, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - sign_with="hotkey", - period=None, - ) - assert result == (True, "") - - def test_immunity_period(subtensor, mocker): """Successful immunity_period call.""" # Preps From 2083d880c4b5b1ca06a599fd21fa129747d4ae02 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 22:08:27 -0700 Subject: [PATCH 130/177] change default values --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/extrinsics/asyncex/serving.py | 2 +- bittensor/core/extrinsics/serving.py | 4 ++-- bittensor/core/subtensor.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ae0ec9a4a2..f6c850a528 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5322,7 +5322,7 @@ async def serve_axon( certificate: Optional[Certificate] = None, period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index e8084b023f..9ff6359022 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -179,7 +179,7 @@ async def serve_axon_extrinsic( certificate: Optional[Certificate] = None, period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 6a8a85f00c..bbec738894 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -129,7 +129,7 @@ def serve_axon_extrinsic( certificate: Optional["Certificate"] = None, period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ @@ -193,10 +193,10 @@ def publish_metadata( netuid: int, data_type: str, data: Union[bytes, dict], + reset_bonds: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, period: Optional[int] = None, - reset_bonds: bool = False, ) -> bool: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b1735c6f38..a455f8ec83 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4119,7 +4119,7 @@ def serve_axon( certificate: Optional[Certificate] = None, period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, + wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ From 3c046592f68e41119dce7cde286a78c296d93057 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 22:47:13 -0700 Subject: [PATCH 131/177] `.publish_metadata`, `subtensor.set_commitment` and `subtenor.set_reveal_commitment` + `subtensor.comit` renamed to `subtensor.set_commitment` + alias `subtensor.set_commitment` removed --- bittensor/core/async_subtensor.py | 115 +++++++++++-------- bittensor/core/extrinsics/asyncex/serving.py | 50 +++++--- bittensor/core/extrinsics/serving.py | 45 ++++---- bittensor/core/subtensor.py | 111 +++++++++++------- bittensor/core/subtensor_api/extrinsics.py | 1 + bittensor/core/subtensor_api/utils.py | 1 - migration.md | 5 +- tests/unit_tests/extrinsics/test_serving.py | 1 + tests/unit_tests/test_subtensor.py | 5 +- 9 files changed, 205 insertions(+), 129 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f6c850a528..bb00e8a4b5 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -930,45 +930,6 @@ async def bonds( return b_map - async def commit( - self, wallet: "Wallet", netuid: int, data: str, period: Optional[int] = None - ) -> bool: - """Commits arbitrary data to the Bittensor network by publishing metadata. - - This method allows neurons to publish arbitrary data to the blockchain, which can be used for various purposes - such as sharing model updates, configuration data, or other network-relevant information. - - Arguments: - wallet: The wallet associated with the neuron committing the data. - netuid: The unique identifier of the subnet. - data: The data to be committed to the network. - period: 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. - - Returns: - bool: True if the commit was successful, False otherwise. - - Example: - # Commit some data to subnet 1 - success = await subtensor.commit(wallet=my_wallet, netuid=1, data="Hello Bittensor!") - - # Commit with custom period - success = await subtensor.commit(wallet=my_wallet, netuid=1, data="Model update v2.0", period=100) - - Note: See - """ - return await publish_metadata( - subtensor=self, - wallet=wallet, - netuid=netuid, - data_type=f"Raw{len(data)}", - data=data.encode(), - period=period, - ) - - set_commitment = commit - async def commit_reveal_enabled( self, netuid: int, @@ -3839,23 +3800,29 @@ async def set_reveal_commitment( blocks_until_reveal: int = 360, block_time: Union[int, float] = 12, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, int]: """ Commits arbitrary data to the Bittensor network by publishing metadata. - Arguments: + Parameters: wallet: The wallet associated with the neuron committing the data. netuid: The unique identifier of the subnetwork. data: The data to be committed to the network. - blocks_until_reveal: The number of blocks from now after which the data will be revealed. - Defaults to ``360`` (the number of blocks in one epoch). - block_time: The number of seconds between each block. Defaults to ``12``. - period: 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. + blocks_until_reveal: The number of blocks from now after which the data will be revealed. Then number of + blocks in one epoch. + block_time: The number of seconds between each block. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: ``True`` if the commitment was successful, ``False`` otherwise. + bool: `True` if the commitment was successful, `False` otherwise. Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. """ @@ -3874,6 +3841,9 @@ async def set_reveal_commitment( data_type="TimelockEncrypted", data=data_, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ), reveal_round async def subnet( @@ -5359,6 +5329,57 @@ async def serve_axon( wait_for_finalization=wait_for_finalization, ) + async def set_commitment( + self, + wallet: "Wallet", + netuid: int, + data: str, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> bool: + """ + Commits arbitrary data to the Bittensor network by publishing metadata. + + This method allows neurons to publish arbitrary data to the blockchain, which can be used for various purposes + such as sharing model updates, configuration data, or other network-relevant information. + + Parameters: + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data. + netuid (int): The unique identifier of the subnetwork. + data (str): The data to be committed to the network. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + bool: `True` if the commitment was successful, `False` otherwise. + + Example: + # Commit some data to subnet 1 + success = await subtensor.commit(wallet=my_wallet, netuid=1, data="Hello Bittensor!") + + # Commit with custom period + success = await subtensor.commit(wallet=my_wallet, netuid=1, data="Model update v2.0", period=100) + + Note: See + """ + return await publish_metadata( + subtensor=self, + wallet=wallet, + netuid=netuid, + data_type=f"Raw{len(data)}", + data=data.encode(), + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + async def start_call( self, wallet: "Wallet", diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 9ff6359022..6a511718d1 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -245,34 +245,34 @@ async def publish_metadata( netuid: int, data_type: str, data: Union[bytes, dict], - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, reset_bonds: bool = False, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. - Args: - subtensor (bittensor.subtensor): The subtensor instance representing the Bittensor blockchain connection. - wallet (bittensor.wallet): The wallet object used for authentication in the transaction. - netuid (int): Network UID on which the metadata is to be published. - data_type (str): The data type of the information being submitted. It should be one of the following: + Parameters: + subtensor: The subtensor instance representing the Bittensor blockchain connection. + wallet: The wallet object used for authentication in the transaction. + netuid: Network UID on which the metadata is to be published. + data_type: The data type of the information being submitted. It should be one of the following: ``'Sha256'``, ``'Blake256'``, ``'Keccak256'``, or ``'Raw0-128'``. This specifies the format or hashing algorithm used for the data. - data (Union[bytes, dict]): The actual metadata content to be published. This should be formatted or hashed + data: The actual metadata content to be published. This should be formatted or hashed according to the ``type`` specified. (Note: max ``str`` length is 128 bytes for ``'Raw0-128'``.) - wait_for_inclusion (bool, optional): If ``True``, the function will wait for the extrinsic to be included in a - block before returning. Defaults to ``False``. - wait_for_finalization (bool, optional): If ``True``, the function will wait for the extrinsic to be finalized - on the chain before returning. Defaults to ``True``. - 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. - reset_bonds (bool): If `True`, the function will reset the bonds for the neuron. Defaults to `False`. + reset_bonds: If `True`, the function will reset the bonds for the neuron. Defaults to `False`. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: ``True`` if the metadata was successfully published (and finalized if specified). ``False`` otherwise. + bool: True if the subnet registration was successful, False otherwise. Raises: MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates @@ -304,6 +304,7 @@ async def publish_metadata( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: @@ -342,7 +343,20 @@ async def get_last_bonds_reset( block_hash: Optional[str] = None, reuse_block: bool = False, ) -> bytes: - """Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid.""" + """ + Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. + + Parameters: + subtensor: Subtensor instance object. + netuid: The network uid to fetch from. + hotkey: The hotkey of the neuron for which to fetch the last bonds reset. + block: The block number to query. If ``None``, the latest block is used. + block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or reuse_block. + reuse_block: Whether to use the last-used block. Do not set if using block_hash or block. + + Returns: + bytes: The last bonds reset data for the specified hotkey and netuid. + """ block_hash = await subtensor.determine_block_hash(block, block_hash, reuse_block) block = await subtensor.substrate.query( module="Commitments", diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index bbec738894..dd62fa3669 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -194,33 +194,33 @@ def publish_metadata( data_type: str, data: Union[bytes, dict], reset_bonds: bool = False, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. - Args: - subtensor (bittensor.subtensor): The subtensor instance representing the Bittensor blockchain connection. - wallet (bittensor.wallet): The wallet object used for authentication in the transaction. - netuid (int): Network UID on which the metadata is to be published. - data_type (str): The data type of the information being submitted. It should be one of the following: + Parameters: + subtensor: The subtensor instance representing the Bittensor blockchain connection. + wallet: The wallet object used for authentication in the transaction. + netuid: Network UID on which the metadata is to be published. + data_type: The data type of the information being submitted. It should be one of the following: ``'Sha256'``, ``'Blake256'``, ``'Keccak256'``, or ``'Raw0-128'``. This specifies the format or hashing algorithm used for the data. - data (Union[bytes, dict]): The actual metadata content to be published. This should be formatted or hashed + data: The actual metadata content to be published. This should be formatted or hashed according to the ``type`` specified. (Note: max ``str`` length is 128 bytes for ``'Raw0-128'``.) - wait_for_inclusion (bool, optional): If ``True``, the function will wait for the extrinsic to be included in a - block before returning. Defaults to ``False``. - wait_for_finalization (bool, optional): If ``True``, the function will wait for the extrinsic to be finalized - on the chain before returning. Defaults to ``True``. - 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. - reset_bonds (bool): If `True`, the function will reset the bonds for the neuron. Defaults to `False`. + reset_bonds: If `True`, the function will reset the bonds for the neuron. Defaults to `False`. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: ``True`` if the metadata was successfully published (and finalized if specified). ``False`` otherwise. + bool: True if the subnet registration was successful, False otherwise. Raises: MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates @@ -251,6 +251,7 @@ def publish_metadata( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if success: @@ -277,11 +278,11 @@ def get_last_bonds_reset( """ Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. - Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance object. - netuid (int): The network uid to fetch from. - hotkey (str): The hotkey of the neuron for which to fetch the last bonds reset. - block (Optional[int]): The block number to query. If ``None``, the latest block is used. + Parameters: + subtensor: Subtensor instance object. + netuid: The network uid to fetch from. + hotkey: The hotkey of the neuron for which to fetch the last bonds reset. + block: The block number to query. If ``None``, the latest block is used. Returns: bytes: The last bonds reset data for the specified hotkey and netuid. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a455f8ec83..3a2ca29cc2 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -530,35 +530,6 @@ def bonds( return b_map - def commit( - self, wallet, netuid: int, data: str, period: Optional[int] = None - ) -> bool: - """ - Commits arbitrary data to the Bittensor network by publishing metadata. - - Arguments: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data. - netuid (int): The unique identifier of the subnetwork. - data (str): The data to be committed to the network. - 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. - - Returns: - bool: `True` if the commitment was successful, `False` otherwise. - """ - return publish_metadata( - subtensor=self, - wallet=wallet, - netuid=netuid, - data_type=f"Raw{len(data)}", - data=data.encode(), - period=period, - ) - - # add explicit alias - set_commitment = commit - def commit_reveal_enabled( self, netuid: int, block: Optional[int] = None ) -> Optional[bool]: @@ -2772,20 +2743,27 @@ def set_reveal_commitment( blocks_until_reveal: int = 360, block_time: Union[int, float] = 12, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, int]: """ Commits arbitrary data to the Bittensor network by publishing metadata. - Arguments: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data. - netuid (int): The unique identifier of the subnetwork. - data (str): The data to be committed to the network. - blocks_until_reveal (int): The number of blocks from now after which the data will be revealed. Defaults to - `360`. Then number of blocks in one epoch. - block_time (Union[int, float]): The number of seconds between each block. Defaults to `12`. - 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. + Parameters: + wallet: The wallet associated with the neuron committing the data. + netuid: The unique identifier of the subnetwork. + data: The data to be committed to the network. + blocks_until_reveal: The number of blocks from now after which the data will be revealed. Then number of + blocks in one epoch. + block_time: The number of seconds between each block. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + Returns: bool: `True` if the commitment was successful, `False` otherwise. @@ -2806,6 +2784,9 @@ def set_reveal_commitment( data_type="TimelockEncrypted", data=data_, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ), reveal_round def subnet(self, netuid: int, block: Optional[int] = None) -> Optional[DynamicInfo]: @@ -4156,6 +4137,58 @@ def serve_axon( wait_for_finalization=wait_for_finalization, ) + def set_commitment( + self, + wallet: "Wallet", + netuid: int, + data: str, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> bool: + """ + Commits arbitrary data to the Bittensor network by publishing metadata. + + This method allows neurons to publish arbitrary data to the blockchain, which can be used for various purposes + such as sharing model updates, configuration data, or other network-relevant information. + + + Parameters: + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data. + netuid (int): The unique identifier of the subnetwork. + data (str): The data to be committed to the network. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + bool: `True` if the commitment was successful, `False` otherwise. + + Example: + # Commit some data to subnet 1 + success = await subtensor.commit(wallet=my_wallet, netuid=1, data="Hello Bittensor!") + + # Commit with custom period + success = await subtensor.commit(wallet=my_wallet, netuid=1, data="Model update v2.0", period=100) + + Note: See + """ + return publish_metadata( + subtensor=self, + wallet=wallet, + netuid=netuid, + data_type=f"Raw{len(data)}", + data=data.encode(), + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + def start_call( self, wallet: "Wallet", diff --git a/bittensor/core/subtensor_api/extrinsics.py b/bittensor/core/subtensor_api/extrinsics.py index c2acf078e5..62c6c5c1b4 100644 --- a/bittensor/core/subtensor_api/extrinsics.py +++ b/bittensor/core/subtensor_api/extrinsics.py @@ -26,6 +26,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.set_subnet_identity = subtensor.set_subnet_identity self.set_weights = subtensor.set_weights self.serve_axon = subtensor.serve_axon + self.set_commitment = subtensor.set_commitment self.start_call = subtensor.start_call self.swap_stake = subtensor.swap_stake self.toggle_user_liquidity = subtensor.toggle_user_liquidity diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index b8383a3f62..34cc48502a 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -15,7 +15,6 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.bonds = subtensor._subtensor.bonds subtensor.burned_register = subtensor._subtensor.burned_register subtensor.chain_endpoint = subtensor._subtensor.chain_endpoint - subtensor.commit = subtensor._subtensor.commit subtensor.commit_reveal_enabled = subtensor._subtensor.commit_reveal_enabled subtensor.commit_weights = subtensor._subtensor.commit_weights subtensor.determine_block_hash = subtensor._subtensor.determine_block_hash diff --git a/migration.md b/migration.md index 29d7bd9783..b94ace9c47 100644 --- a/migration.md +++ b/migration.md @@ -190,4 +190,7 @@ wait_for_finalization: bool = False, - [x] `.set_subnet_identity_extrinsic` and `subtensor.set_subnet_identity` - [x] `.root_register_extrinsic`, `subtensor.burned_register` and `subtensor.root_register` - [x] `.serve_extrinsic` -- [x] `.serve_axon_extrinsic` and `subtensor.serve_axon` \ No newline at end of file +- [x] `.serve_axon_extrinsic` and `subtensor.serve_axon` +- [x] alias `subtensor.set_commitment` removed +- [x] `subtensor.comit` renamed to `subtensor.set_commitment` +- [x] `.publish_metadata`, `subtensor.set_commitment` and `subtenor.set_reveal_commitment` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 4fca1b7bc8..3964df1cd2 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -386,4 +386,5 @@ def test_publish_metadata( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=None, + raise_error=False, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index d9a1a6d401..1ee5971654 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1262,7 +1262,7 @@ def test_commit(subtensor, fake_wallet, mocker): mocked_publish_metadata = mocker.patch.object(subtensor_module, "publish_metadata") # Call - result = subtensor.commit(fake_wallet, fake_netuid, fake_data) + result = subtensor.set_commitment(fake_wallet, fake_netuid, fake_data) # Asserts mocked_publish_metadata.assert_called_once_with( @@ -1272,6 +1272,9 @@ def test_commit(subtensor, fake_wallet, mocker): data_type=f"Raw{len(fake_data)}", data=fake_data.encode(), period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, ) assert result is mocked_publish_metadata.return_value From a6f6aa0883b9d49a62a40ca587b59dc4dc21c946 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 23:33:11 -0700 Subject: [PATCH 132/177] `.add_stake_extrinsic`, `subtensor.add_stake` + `.add_stake_multiple_extrinsic`, `subtensor.add_stake_multiple` --- bittensor/core/async_subtensor.py | 46 ++++++----- bittensor/core/extrinsics/asyncex/staking.py | 83 +++++++++---------- bittensor/core/extrinsics/staking.py | 84 +++++++++++--------- bittensor/core/subtensor.py | 65 ++++++++------- migration.md | 4 +- tests/unit_tests/extrinsics/test_staking.py | 6 +- tests/unit_tests/test_subtensor.py | 3 + 7 files changed, 158 insertions(+), 133 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index bb00e8a4b5..f95ab6381a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4279,36 +4279,38 @@ async def add_stake( hotkey_ss58: Optional[str] = None, netuid: Optional[int] = None, amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn incentives. - Arguments: + Parameters: wallet: The wallet to be used for staking. hotkey_ss58: The SS58 address of the hotkey associated with the neuron to which you intend to delegate your - stake. If not specified, the wallet's hotkey will be used. Defaults to ``None``. + stake. If not specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. amount: The amount of TAO to stake. - wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to `False`. safe_staking: If true, enables price safety checks to protect against fluctuating prices. The stake will only execute if the price change doesn't exceed the rate tolerance. Default is ``False``. allow_partial_stake: If true and safe_staking is enabled, allows partial staking when the full amount would exceed the price tolerance. If false, the entire stake fails if it would exceed the tolerance. Default is ``False``. rate_tolerance: The maximum allowed price change ratio when staking. For example, 0.005 = 0.5% maximum price - increase. Only used when safe_staking is True. Default is ``0.005``. - period: 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. Defaults to ``None``. + increase. Only used when safe_staking is True. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: bool: ``True`` if the staking is successful, ``False`` otherwise. @@ -4324,12 +4326,13 @@ async def add_stake( hotkey_ss58=hotkey_ss58, netuid=netuid, amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) async def add_liquidity( @@ -4390,9 +4393,10 @@ async def add_stake_multiple( hotkey_ss58s: list[str], netuids: list[int], amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Adds stakes to multiple neurons identified by their hotkey SS58 addresses. @@ -4403,11 +4407,12 @@ async def add_stake_multiple( hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. netuids: list of subnet UIDs. amounts: Corresponding amounts of TAO to stake for each hotkey. - wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to `False`. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: bool: ``True`` if the staking is successful for all specified neurons, ``False`` otherwise. @@ -4421,9 +4426,10 @@ async def add_stake_multiple( hotkey_ss58s=hotkey_ss58s, netuids=netuids, amounts=amounts, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def burned_register( diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 76ffe73285..f32d9f1a0c 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -20,39 +20,39 @@ async def add_stake_extrinsic( hotkey_ss58: Optional[str] = None, netuid: Optional[int] = None, amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. - Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn incentives. + Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn + incentives. - Arguments: + Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object. old_balance: the balance prior to the staking hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. If not - specified, the wallet's hotkey will be used. Defaults to ``None``. + specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. - amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. Defaults is ``None``. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. Defaults to ``True``. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, - or returns `False` if the extrinsic fails to be finalized within the timeout. Defaults to ``False``. + amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. safe_staking: If True, enables price safety checks. Default is ``False``. allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. Default is ``False``. rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). Default is ``0.005``. - period: 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. Defaults to ``None``. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for - finalization/inclusion, the response is `True`. + bool: True if the subnet registration was successful, False otherwise. Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. @@ -158,7 +158,7 @@ async def add_stake_extrinsic( call_function=call_function, call_params=call_params, ) - staking_response, err_msg = await subtensor.sign_and_send_extrinsic( + success, message = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -167,8 +167,9 @@ async def add_stake_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) - if staking_response is True: # If we successfully staked. + if success: # If we successfully staked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: return True @@ -199,14 +200,14 @@ async def add_stake_extrinsic( f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" ) return True + + if safe_staking and "Custom error: 8" in message: + logging.error( + ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." + ) else: - if safe_staking and "Custom error: 8" in err_msg: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {err_msg}.[/red]") - return False + logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + return False except SubstrateRequestException as error: logging.error( @@ -222,30 +223,30 @@ async def add_stake_multiple_extrinsic( netuids: list[int], old_balance: Optional[Balance] = None, amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = True, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Adds a stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. + """ + Adds a stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. - Arguments: - subtensor: The initialized SubtensorInterface object. + Parameters: + subtensor: AsyncSubtensor instance with the connection to the chain. wallet: Bittensor wallet object for the coldkey. old_balance: The balance of the wallet prior to staking. hotkey_ss58s: List of hotkeys to stake to. netuids: List of netuids to stake to. amounts: List of amounts to stake. If `None`, stake all to the first hotkey. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns `False` - if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, or - returns `False` if the extrinsic fails to be finalized within the timeout. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: `True` if extrinsic was finalized or included in the block. `True` if any wallet was staked. If we did - not wait for finalization/inclusion, the response is `True`. + bool: True if the subnet registration was successful, False otherwise. """ if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s @@ -362,11 +363,11 @@ async def add_stake_multiple_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) - if success is True: # If we successfully staked. - # We only wait here if we expect finalization. - + # If we successfully staked. + if success: if not wait_for_finalization and not wait_for_inclusion: old_balance -= staking_balance successful_stakes += 1 diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index fc8b69c48b..fc08c90871 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -15,15 +15,17 @@ def add_stake_extrinsic( subtensor: "Subtensor", wallet: "Wallet", + old_balance: Optional[Balance] = None, hotkey_ss58: Optional[str] = None, netuid: Optional[int] = None, amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. @@ -32,24 +34,23 @@ def add_stake_extrinsic( Arguments: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object. + old_balance: the balance prior to the staking hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. If not - specified, the wallet's hotkey will be used. Defaults to ``None``. + specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. - amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. Defaults is ``None``. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. Defaults to ``True``. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, - or returns `False` if the extrinsic fails to be finalized within the timeout. Defaults to ``False``. + amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. safe_staking: If True, enables price safety checks. Default is ``False``. allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. Default is ``False``. rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). Default is ``0.005``. - period: 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. Defaults to ``None``. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for - finalization/inclusion, the response is `True`. + bool: True if the subnet registration was successful, False otherwise. Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. @@ -67,7 +68,8 @@ def add_stake_extrinsic( logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + if not old_balance: + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) block = subtensor.get_current_block() # Get current stake and existential deposit @@ -160,6 +162,7 @@ def add_stake_extrinsic( sign_with="coldkey", nonce_key="coldkeypub", period=period, + raise_error=raise_error, ) if success is True: # If we successfully staked. # We only wait here if we expect finalization. @@ -189,14 +192,14 @@ def add_stake_extrinsic( f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" ) return True + + if safe_staking and "Custom error: 8" in message: + logging.error( + ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." + ) else: - if safe_staking and "Custom error: 8" in message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") - return False + logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + return False except SubstrateRequestException as error: logging.error( @@ -210,32 +213,33 @@ def add_stake_multiple_extrinsic( wallet: "Wallet", hotkey_ss58s: list[str], netuids: list[int], + old_balance: Optional[Balance] = None, amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. + """ + Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. - Arguments: - subtensor: The initialized SubtensorInterface object. + Parameters: + subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object for the coldkey. + old_balance: The balance of the wallet prior to staking. hotkey_ss58s: List of hotkeys to stake to. netuids: List of netuids to stake to. amounts: List of amounts to stake. If `None`, stake all to the first hotkey. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns `False` - if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`, or - returns `False` if the extrinsic fails to be finalized within the timeout. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: `True` if extrinsic was finalized or included in the block. `True` if any wallet was staked. If we did - not wait for finalization/inclusion, the response is `True`. + bool: True if the subnet registration was successful, False otherwise. """ - if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s ): @@ -283,9 +287,10 @@ def add_stake_multiple_extrinsic( total_staking_rao = sum( [amount.rao if amount is not None else 0 for amount in new_amounts] ) - old_balance = initial_balance = subtensor.get_balance( - wallet.coldkeypub.ss58_address, block=block - ) + if old_balance is None: + old_balance = initial_balance = subtensor.get_balance( + wallet.coldkeypub.ss58_address, block=block + ) if total_staking_rao == 0: # Staking all to the first wallet. if old_balance.rao > 1000: @@ -344,6 +349,7 @@ def add_stake_multiple_extrinsic( nonce_key="coldkeypub", sign_with="coldkey", period=period, + raise_error=raise_error, ) if success is True: # If we successfully staked. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 3a2ca29cc2..dfad565985 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3120,44 +3120,45 @@ def add_stake( hotkey_ss58: Optional[str] = None, netuid: Optional[int] = None, amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified - subnet. Staking is a fundamental process in the Bittensor network that enables neurons to participate - actively and earn incentives. + subnet. Staking is a fundamental process in the Bittensor network that enables neurons to participate actively + and earn incentives. - Args: + Parameters: wallet: The wallet to be used for staking. hotkey_ss58: The SS58 address of the hotkey associated with the neuron to which you intend to delegate your - stake. If not specified, the wallet's hotkey will be used. Defaults to ``None``. + stake. If not specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. amount: The amount of TAO to stake. - wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to ``True``. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to ``False``. safe_staking: If true, enables price safety checks to protect against fluctuating prices. The stake will only execute if the price change doesn't exceed the rate tolerance. Default is ``False``. allow_partial_stake: If true and safe_staking is enabled, allows partial staking when the full amount would exceed the price tolerance. If false, the entire stake fails if it would exceed the tolerance. Default is ``False``. - rate_tolerance: The maximum allowed price change ratio when staking. For example, - 0.005 = 0.5% maximum price increase. Only used when safe_staking is True. Default is ``0.005``. - period: 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. Defaults to ``None``. + rate_tolerance: The maximum allowed price change ratio when staking. For example, 0.005 = 0.5% maximum price + increase. Only used when safe_staking is True. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: True if the staking is successful, False otherwise. + bool: ``True`` if the staking is successful, ``False`` otherwise. - This function enables neurons to increase their stake in the network, enhancing their influence and potential - rewards in line with Bittensor's consensus and reward mechanisms. - When safe_staking is enabled, it provides protection against price fluctuations during the time stake is - executed and the time it is actually processed by the chain. + This function enables neurons to increase their stake in the network, enhancing their influence and potential. + When safe_staking is enabled, it provides protection against price fluctuations during the time stake is + executed and the time it is actually processed by the chain. """ amount = check_and_convert_to_balance(amount) return add_stake_extrinsic( @@ -3166,12 +3167,13 @@ def add_stake( hotkey_ss58=hotkey_ss58, netuid=netuid, amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) def add_liquidity( @@ -3232,30 +3234,32 @@ def add_stake_multiple( hotkey_ss58s: list[str], netuids: list[int], amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Adds stakes to multiple neurons identified by their hotkey SS58 addresses. This bulk operation allows for efficient staking across different neurons from a single wallet. - Parameters: + Arguments: wallet: The wallet used for staking. hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. - netuids: List of network UIDs to stake to. + netuids: list of subnet UIDs. amounts: Corresponding amounts of TAO to stake for each hotkey. + period: 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 a relevant exception rather than returning `False` if unsuccessful. wait_for_inclusion: Waits for the transaction to be included in a block. wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - period: 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. Returns: - bool: ``True`` if the staking is successful for all specified neurons, False otherwise. + bool: ``True`` if the staking is successful for all specified neurons, ``False`` otherwise. This function is essential for managing stakes across multiple neurons, reflecting the dynamic and collaborative - nature of the Bittensor network. + nature of the Bittensor network. """ return add_stake_multiple_extrinsic( subtensor=self, @@ -3263,9 +3267,10 @@ def add_stake_multiple( hotkey_ss58s=hotkey_ss58s, netuids=netuids, amounts=amounts, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def burned_register( diff --git a/migration.md b/migration.md index b94ace9c47..8762090d66 100644 --- a/migration.md +++ b/migration.md @@ -193,4 +193,6 @@ wait_for_finalization: bool = False, - [x] `.serve_axon_extrinsic` and `subtensor.serve_axon` - [x] alias `subtensor.set_commitment` removed - [x] `subtensor.comit` renamed to `subtensor.set_commitment` -- [x] `.publish_metadata`, `subtensor.set_commitment` and `subtenor.set_reveal_commitment` \ No newline at end of file +- [x] `.publish_metadata`, `subtensor.set_commitment` and `subtenor.set_reveal_commitment` +- [x] `.add_stake_extrinsic`, `subtensor.add_stake` +- [x] `.add_stake_multiple_extrinsic`, `subtensor.add_stake_multiple` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 80ecb5c240..9ba6fe71a6 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -52,6 +52,7 @@ def test_add_stake_extrinsic(mocker): sign_with="coldkey", use_nonce=True, period=None, + raise_error=False, ) @@ -134,10 +135,11 @@ def test_add_stake_multiple_extrinsic(mocker): fake_subtensor.sign_and_send_extrinsic.assert_called_with( call=fake_subtensor.substrate.compose_call.return_value, wallet=fake_wallet_, - wait_for_inclusion=True, - wait_for_finalization=True, nonce_key="coldkeypub", sign_with="coldkey", use_nonce=True, period=None, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=True, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 1ee5971654..648f519b8d 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2746,6 +2746,7 @@ def test_add_stake_success(mocker, fake_wallet, subtensor): allow_partial_stake=False, rate_tolerance=0.005, period=None, + raise_error=False, ) assert result == mock_add_stake_extrinsic.return_value @@ -2786,6 +2787,7 @@ def test_add_stake_with_safe_staking(mocker, fake_wallet, subtensor): allow_partial_stake=False, rate_tolerance=fake_rate_tolerance, period=None, + raise_error=False, ) assert result == mock_add_stake_extrinsic.return_value @@ -2820,6 +2822,7 @@ def test_add_stake_multiple_success(mocker, fake_wallet, subtensor): wait_for_inclusion=True, wait_for_finalization=False, period=None, + raise_error=False, ) assert result == mock_add_stake_multiple_extrinsic.return_value From 1cc391be2243b09efb9a853fb199a1e82e5490a5 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 23:44:16 -0700 Subject: [PATCH 133/177] `.start_call_extrinsic`, `subtensor.start_call` --- bittensor/core/async_subtensor.py | 17 +++++++------ .../core/extrinsics/asyncex/start_call.py | 17 +++++++------ bittensor/core/extrinsics/start_call.py | 11 +++++--- bittensor/core/subtensor.py | 25 ++++++++++--------- migration.md | 3 ++- .../extrinsics/asyncex/test_start_call.py | 1 + .../unit_tests/extrinsics/test_start_call.py | 1 + tests/unit_tests/test_async_subtensor.py | 1 + tests/unit_tests/test_subtensor.py | 1 + 9 files changed, 46 insertions(+), 31 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f95ab6381a..4b915410a1 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5390,22 +5390,24 @@ async def start_call( self, wallet: "Wallet", netuid: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a new subnet's emission mechanism). - Arguments: + Parameters: wallet: The wallet used to sign the extrinsic (must be unlocked). netuid: The UID of the target subnet for which the call is being initiated. - wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. Defaults to `True`. - wait_for_finalization: Whether to wait for finalization of the extrinsic. Defaults to `False`. period: 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. + 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -5416,9 +5418,10 @@ async def start_call( subtensor=self, wallet=wallet, netuid=netuid, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def swap_stake( diff --git a/bittensor/core/extrinsics/asyncex/start_call.py b/bittensor/core/extrinsics/asyncex/start_call.py index 63f6fbc3c1..3a2c64937a 100644 --- a/bittensor/core/extrinsics/asyncex/start_call.py +++ b/bittensor/core/extrinsics/asyncex/start_call.py @@ -12,23 +12,25 @@ async def start_call_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a new subnet's emission mechanism). - Args: - subtensor (Subtensor): The Subtensor client instance used for blockchain interaction. - wallet (Wallet): The wallet used to sign the extrinsic (must be unlocked). - netuid (int): The UID of the target subnet for which the call is being initiated. - wait_for_inclusion (bool, optional): Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization (bool, optional): Whether to wait for finalization of the extrinsic. Defaults to False. + Parameters: + subtensor: The Subtensor client instance used for blockchain interaction. + wallet: The wallet used to sign the extrinsic (must be unlocked). + netuid: The UID of the target subnet for which the call is being initiated. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -52,6 +54,7 @@ async def start_call_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/extrinsics/start_call.py b/bittensor/core/extrinsics/start_call.py index 2788bb88f1..4a0b37e382 100644 --- a/bittensor/core/extrinsics/start_call.py +++ b/bittensor/core/extrinsics/start_call.py @@ -12,23 +12,25 @@ def start_call_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a new subnet's emission mechanism). - Args: + Parameters: subtensor (Subtensor): The Subtensor client instance used for blockchain interaction. wallet (Wallet): The wallet used to sign the extrinsic (must be unlocked). netuid (int): The UID of the target subnet for which the call is being initiated. - wait_for_inclusion (bool, optional): Whether to wait for the extrinsic to be included in a block. Defaults to True. - wait_for_finalization (bool, optional): Whether to wait for finalization of the extrinsic. Defaults to False. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -51,6 +53,7 @@ def start_call_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, + raise_error=raise_error, ) if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index dfad565985..2dc16bd679 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4198,24 +4198,24 @@ def start_call( self, wallet: "Wallet", netuid: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Submits a start_call extrinsic to the blockchain, to trigger the start call process for a subnet (used to start a new subnet's emission mechanism). - Args: - wallet (Wallet): The wallet used to sign the extrinsic (must be unlocked). - netuid (int): The UID of the target subnet for which the call is being initiated. - wait_for_inclusion (bool, optional): Whether to wait for the extrinsic to be included in a block. - Defaults to `True`. - wait_for_finalization (bool, optional): Whether to wait for finalization of the extrinsic. - Defaults to `False`. - 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. + Parameters: + wallet: The wallet used to sign the extrinsic (must be unlocked). + netuid: The UID of the target subnet for which the call is being initiated. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: Tuple[bool, str]: @@ -4226,9 +4226,10 @@ def start_call( subtensor=self, wallet=wallet, netuid=netuid, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def swap_stake( diff --git a/migration.md b/migration.md index 8762090d66..2eb52422b2 100644 --- a/migration.md +++ b/migration.md @@ -195,4 +195,5 @@ wait_for_finalization: bool = False, - [x] `subtensor.comit` renamed to `subtensor.set_commitment` - [x] `.publish_metadata`, `subtensor.set_commitment` and `subtenor.set_reveal_commitment` - [x] `.add_stake_extrinsic`, `subtensor.add_stake` -- [x] `.add_stake_multiple_extrinsic`, `subtensor.add_stake_multiple` \ No newline at end of file +- [x] `.add_stake_multiple_extrinsic`, `subtensor.add_stake_multiple` +- [x] `.start_call_extrinsic`, `subtensor.start_call` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_start_call.py b/tests/unit_tests/extrinsics/asyncex/test_start_call.py index d99295195e..68507acbd2 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_start_call.py +++ b/tests/unit_tests/extrinsics/asyncex/test_start_call.py @@ -36,6 +36,7 @@ async def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_inclusion=True, wait_for_finalization=False, period=None, + raise_error=False, ) assert success is True diff --git a/tests/unit_tests/extrinsics/test_start_call.py b/tests/unit_tests/extrinsics/test_start_call.py index ece0e6cf42..03373fa955 100644 --- a/tests/unit_tests/extrinsics/test_start_call.py +++ b/tests/unit_tests/extrinsics/test_start_call.py @@ -34,6 +34,7 @@ def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_inclusion=True, wait_for_finalization=False, period=None, + raise_error=False, ) assert success is True diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index ecc791bfaa..662aa4e8a8 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3113,6 +3113,7 @@ async def test_start_call(subtensor, mocker): wait_for_inclusion=True, wait_for_finalization=False, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 648f519b8d..71210f6b5e 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3236,6 +3236,7 @@ def test_start_call(subtensor, mocker): wait_for_inclusion=True, wait_for_finalization=False, period=None, + raise_error=False, ) assert result == mocked_extrinsic.return_value From 38201475c9cc9facf3815363502ddc3b9d86b668 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 00:14:13 -0700 Subject: [PATCH 134/177] fix `test_dendrite` --- tests/e2e_tests/test_dendrite.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index c4a8085444..fcd601577d 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -15,6 +15,9 @@ wait_to_start_call, ) +logging.on() +logging.set_debug() + @pytest.mark.asyncio async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): @@ -108,7 +111,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): ) assert subtensor.staking.add_stake( - bob_wallet, + wallet=bob_wallet, netuid=alice_subnet_netuid, amount=tao, ) @@ -188,6 +191,8 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall wallet=alice_wallet, netuid=alice_subnet_netuid, amount=Balance.from_tao(1), + wait_for_inclusion=False, + wait_for_finalization=False, ) # update max_allowed_validators so only one neuron can get validator_permit @@ -242,9 +247,11 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall ).tao_to_alpha_with_slippage(tao) assert await async_subtensor.staking.add_stake( - bob_wallet, + wallet=bob_wallet, netuid=alice_subnet_netuid, amount=tao, + wait_for_inclusion=False, + wait_for_finalization=False, ) # Refresh metagraph From 162e30dc95e859de0137db12f73d039253480ce3 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 00:54:12 -0700 Subject: [PATCH 135/177] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` --- bittensor/core/async_subtensor.py | 59 +++++++++---------- bittensor/core/extrinsics/asyncex/take.py | 69 ++++++++++++----------- bittensor/core/extrinsics/take.py | 69 ++++++++++++----------- bittensor/core/subtensor.py | 60 +++++++++----------- migration.md | 7 ++- 5 files changed, 131 insertions(+), 133 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4b915410a1..d46bfb13a7 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5035,30 +5035,30 @@ async def set_delegate_take( wallet: "Wallet", hotkey_ss58: str, take: float, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - raise_error: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Sets the delegate 'take' percentage for a neuron identified by its hotkey. The 'take' represents the percentage of rewards that the delegate claims from its nominators' stakes. - Arguments: + Parameters: wallet: bittensor wallet instance. hotkey_ss58: The ``SS58`` address of the neuron's hotkey. take: Percentage reward for the delegate. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on_error: Raises a relevant exception - rather than returning ``False`` if unsuccessful. - raise_error: raises a relevant exception rather than returning ``False`` if unsuccessful. period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. Raises: DelegateTakeTooHigh: Delegate take is too high. @@ -5073,7 +5073,6 @@ async def set_delegate_take( The delegate take is a critical parameter in the network's incentive structure, influencing the distribution of rewards among neurons and their nominators. """ - # u16 representation of the take take_u16 = int(take * 0xFFFF) @@ -5086,33 +5085,27 @@ async def set_delegate_take( logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - if current_take_u16 < take_u16: - success, error = await increase_take_extrinsic( - self, - wallet, - hotkey_ss58, - take_u16, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - raise_error=raise_error, - period=period, - ) - else: - success, error = await decrease_take_extrinsic( - self, - wallet, - hotkey_ss58, - take_u16, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - raise_error=raise_error, - period=period, - ) + extrinsic_call = ( + increase_take_extrinsic + if current_take_u16 < take_u16 + else decrease_take_extrinsic + ) + + success, message = await extrinsic_call( + subtensor=self, + wallet=wallet, + hotkey_ss58=hotkey_ss58, + take=take_u16, + period=period, + raise_error=raise_error, + wait_for_finalization=wait_for_finalization, + wait_for_inclusion=wait_for_inclusion, + ) if success: logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") - return success, error + return success, message async def set_subnet_identity( self, diff --git a/bittensor/core/extrinsics/asyncex/take.py b/bittensor/core/extrinsics/asyncex/take.py index 543d4e72da..3840821c08 100644 --- a/bittensor/core/extrinsics/asyncex/take.py +++ b/bittensor/core/extrinsics/asyncex/take.py @@ -13,27 +13,29 @@ async def increase_take_extrinsic( wallet: Wallet, hotkey_ss58: str, take: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - raise_error: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """Sets the delegate 'take' percentage for a neuron identified by its hotkey. - Args: - subtensor (Subtensor): Blockchain connection. - wallet (Wallet): The wallet to sign the extrinsic. - hotkey_ss58 (str): SS58 address of the hotkey to set take for. - take (int): The percentage of rewards that the delegate claims from nominators. - wait_for_inclusion (bool, optional): Wait for inclusion before returning. Defaults to True. - wait_for_finalization (bool, optional): Wait for finalization before returning. Defaults to True. - raise_error (bool, optional): Raise error on failure. Defaults to False. - period: 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. + Parameters: + subtensor: The Subtensor instance. + wallet: The wallet to sign the extrinsic. + hotkey_ss58: SS58 address of the hotkey to set take for. + take: The percentage of rewards that the delegate claims from nominators. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: Success flag and status message. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet, raise_error=raise_error) @@ -65,27 +67,30 @@ async def decrease_take_extrinsic( wallet: Wallet, hotkey_ss58: str, take: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - raise_error: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: - """Sets the delegate 'take' percentage for a neuron identified by its hotkey. - - Args: - subtensor (Subtensor): Blockchain connection. - wallet (Wallet): The wallet to sign the extrinsic. - hotkey_ss58 (str): SS58 address of the hotkey to set take for. - take (int): The percentage of rewards that the delegate claims from nominators. - wait_for_inclusion (bool, optional): Wait for inclusion before returning. Defaults to True. - wait_for_finalization (bool, optional): Wait for finalization before returning. Defaults to True. - raise_error (bool, optional): Raise error on failure. Defaults to False. - period: 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. + """ + Sets the delegate 'take' percentage for a neuron identified by its hotkey. + + Parameters: + subtensor: The Subtensor instance. + wallet: The wallet to sign the extrinsic. + hotkey_ss58: SS58 address of the hotkey to set take for. + take: The percentage of rewards that the delegate claims from nominators. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: Success flag and status message. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet, raise_error=raise_error) @@ -104,8 +109,8 @@ async def decrease_take_extrinsic( return await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) diff --git a/bittensor/core/extrinsics/take.py b/bittensor/core/extrinsics/take.py index 968338d8a9..3effd7699b 100644 --- a/bittensor/core/extrinsics/take.py +++ b/bittensor/core/extrinsics/take.py @@ -13,27 +13,29 @@ def increase_take_extrinsic( wallet: Wallet, hotkey_ss58: str, take: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - raise_error: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: """Sets the delegate 'take' percentage for a neuron identified by its hotkey. - Args: - subtensor (Subtensor): Blockchain connection. - wallet (Wallet): The wallet to sign the extrinsic. - hotkey_ss58 (str): SS58 address of the hotkey to set take for. - take (int): The percentage of rewards that the delegate claims from nominators. - wait_for_inclusion (bool, optional): Wait for inclusion before returning. Defaults to True. - wait_for_finalization (bool, optional): Wait for finalization before returning. Defaults to True. - raise_error (bool, optional): Raise error on failure. Defaults to False. - period: 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. + Parameters: + subtensor: The Subtensor instance. + wallet: The wallet to sign the extrinsic. + hotkey_ss58: SS58 address of the hotkey to set take for. + take: The percentage of rewards that the delegate claims from nominators. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: Success flag and status message. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet, raise_error=raise_error) @@ -64,27 +66,30 @@ def decrease_take_extrinsic( wallet: Wallet, hotkey_ss58: str, take: int, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - raise_error: bool = False, - period: Optional[int] = None, ) -> tuple[bool, str]: - """Sets the delegate `take` percentage for a neuron identified by its hotkey. - - Args: - subtensor (Subtensor): Blockchain connection. - wallet (Wallet): The wallet to sign the extrinsic. - hotkey_ss58 (str): SS58 address of the hotkey to set take for. - take (int): The percentage of rewards that the delegate claims from nominators. - wait_for_inclusion (bool, optional): Wait for inclusion before returning. Defaults to True. - wait_for_finalization (bool, optional): Wait for finalization before returning. Defaults to True. - raise_error (bool, optional): Raise error on failure. Defaults to False. - 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. + """ + Sets the delegate `take` percentage for a neuron identified by its hotkey. + + Parameters: + subtensor: The Subtensor instance. + wallet: The wallet to sign the extrinsic. + hotkey_ss58: SS58 address of the hotkey to set take for. + take: The percentage of rewards that the delegate claims from nominators. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: Success flag and status message. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet, raise_error=raise_error) @@ -103,8 +108,8 @@ def decrease_take_extrinsic( return subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, - wait_for_inclusion=wait_for_inclusion, + period=period, raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 2dc16bd679..2d50e9b819 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3867,19 +3867,20 @@ def set_delegate_take( The 'take' represents the percentage of rewards that the delegate claims from its nominators' stakes. Parameters: - wallet (bittensor_wallet.Wallet): bittensor wallet instance. - hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. - take (float): Percentage reward for the delegate. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. - period (Optional[int]): The number of blocks during which the transaction will remain valid after it's + wallet: bittensor wallet instance. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + take: Percentage reward for the delegate. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: A tuple where the first element is a boolean indicating success or failure of the - operation, and the second element is a message providing additional information. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. Raises: DelegateTakeTooHigh: Delegate take is too high. @@ -3894,7 +3895,6 @@ def set_delegate_take( The delegate take is a critical parameter in the network's incentive structure, influencing the distribution of rewards among neurons and their nominators. """ - # u16 representation of the take take_u16 = int(take * 0xFFFF) @@ -3907,33 +3907,27 @@ def set_delegate_take( logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - if current_take_u16 < take_u16: - success, error = increase_take_extrinsic( - self, - wallet, - hotkey_ss58, - take_u16, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - raise_error=raise_error, - period=period, - ) - else: - success, error = decrease_take_extrinsic( - self, - wallet, - hotkey_ss58, - take_u16, - wait_for_finalization=wait_for_finalization, - wait_for_inclusion=wait_for_inclusion, - raise_error=raise_error, - period=period, - ) + extrinsic_call = ( + increase_take_extrinsic + if current_take_u16 < take_u16 + else decrease_take_extrinsic + ) + + success, message = extrinsic_call( + subtensor=self, + wallet=wallet, + hotkey_ss58=hotkey_ss58, + take=take_u16, + period=period, + raise_error=raise_error, + wait_for_finalization=wait_for_finalization, + wait_for_inclusion=wait_for_inclusion, + ) if success: logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") - return success, error + return success, message def set_subnet_identity( self, diff --git a/migration.md b/migration.md index 2eb52422b2..64a2ba3960 100644 --- a/migration.md +++ b/migration.md @@ -194,6 +194,7 @@ wait_for_finalization: bool = False, - [x] alias `subtensor.set_commitment` removed - [x] `subtensor.comit` renamed to `subtensor.set_commitment` - [x] `.publish_metadata`, `subtensor.set_commitment` and `subtenor.set_reveal_commitment` -- [x] `.add_stake_extrinsic`, `subtensor.add_stake` -- [x] `.add_stake_multiple_extrinsic`, `subtensor.add_stake_multiple` -- [x] `.start_call_extrinsic`, `subtensor.start_call` \ No newline at end of file +- [x] `.add_stake_extrinsic` and `subtensor.add_stake` +- [x] `.add_stake_multiple_extrinsic` and `subtensor.add_stake_multiple` +- [x] `.start_call_extrinsic` and `subtensor.start_call` +- [x] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` \ No newline at end of file From 6f17d02992633eff50c6cc8afad99d12cc993950 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 01:50:10 -0700 Subject: [PATCH 136/177] no flaky behavior for `tests/e2e_tests/test_dendrite.py` --- tests/e2e_tests/test_dendrite.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index fcd601577d..d78531ef75 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -52,12 +52,13 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): "Subnet is not active." ) - # Make sure Alice is Top Validator - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - amount=Balance.from_tao(1), - ) + # Make sure Alice is Top Validator (for non-fast-runtime only) + if subtensor.chain.is_fast_blocks(): + 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( @@ -118,6 +119,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) + metagraph.sync() bob_neuron = metagraph.neurons[1] # Assert alpha is close to stake equivalent @@ -186,14 +188,15 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall "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), - wait_for_inclusion=False, - wait_for_finalization=False, - ) + # Make sure Alice is Top Validator (for non-fast-runtime only) + if await async_subtensor.chain.is_fast_blocks(): + assert await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + amount=Balance.from_tao(1), + wait_for_inclusion=False, + wait_for_finalization=False, + ) # update max_allowed_validators so only one neuron can get validator_permit assert await async_sudo_set_admin_utils( From 7626866332c6c71e7004f37c752cd1659a482f6b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 03:19:23 -0700 Subject: [PATCH 137/177] `.transfer_extrinsic` and `subtensor.transfer` --- bittensor/core/async_subtensor.py | 24 ++++++----- bittensor/core/extrinsics/asyncex/transfer.py | 41 +++++++++--------- bittensor/core/extrinsics/transfer.py | 42 +++++++++---------- bittensor/core/subtensor.py | 34 ++++++++------- migration.md | 3 +- .../extrinsics/asyncex/test_transfer.py | 2 + tests/unit_tests/extrinsics/test_transfer.py | 2 + tests/unit_tests/test_async_subtensor.py | 1 + tests/unit_tests/test_subtensor.py | 1 + 9 files changed, 81 insertions(+), 69 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d8c47b61ec..875615573f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5531,25 +5531,28 @@ async def transfer( destination: str, amount: Optional[Balance], transfer_all: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, keep_alive: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, ) -> bool: """ Transfer token of amount to destination. - Arguments: + Parameters: wallet: Source wallet for the transfer. destination: Destination address for the transfer. amount: Number of tokens to transfer. `None` is transferring all. transfer_all: Flag to transfer all tokens. Default is `False`. - wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to `False`. keep_alive: Flag to keep the connection alive. Default is `True`. - period: 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. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. + Returns: `True` if the transferring was successful, otherwise `False`. """ @@ -5561,10 +5564,11 @@ async def transfer( destination=destination, amount=amount, transfer_all=transfer_all, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, keep_alive=keep_alive, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) async def transfer_stake( diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index 365441d51c..3e3d6d7193 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -4,9 +4,9 @@ from bittensor.core.settings import NETWORK_EXPLORER_MAP from bittensor.utils import ( get_explorer_url_for_network, + get_transfer_fn_params, is_valid_bittensor_address_or_public_key, unlock_key, - get_transfer_fn_params, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -21,33 +21,31 @@ async def transfer_extrinsic( wallet: "Wallet", destination: str, amount: Optional[Balance], - transfer_all: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, keep_alive: bool = True, + transfer_all: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """Transfers funds from this wallet to the destination public key address. - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): initialized AsyncSubtensor object used for transfer - wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from. - destination (str): Destination public key address (ss58_address or ed25519) of recipient. - amount (Optional[bittensor.utils.balance.Balance]): Amount to stake as Bittensor balance. `None` if - transferring all. - transfer_all (bool): Whether to transfer all funds from this wallet to the destination address. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - keep_alive (bool): If set, keeps the account alive by keeping the balance above the existential deposit. - 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. + Parameters: + subtensor: The Subtensor instance. + wallet: The wallet to sign the extrinsic. + destination: Destination public key address (ss58_address or ed25519) of recipient. + amount: Amount to stake as Bittensor balance. `None` if transferring all. + transfer_all: Whether to transfer all funds from this wallet to the destination address. + keep_alive: If set, keeps the account alive by keeping the balance above the existential deposit. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is `True`, regardless of its inclusion. + bool: True if the subnet registration was successful, False otherwise. """ if amount is None and not transfer_all: logging.error("If not transferring all, `amount` must be specified.") @@ -114,6 +112,7 @@ async def transfer_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/transfer.py b/bittensor/core/extrinsics/transfer.py index 0d0a8377b5..7d2a28ad9f 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -2,10 +2,10 @@ from bittensor.core.settings import NETWORK_EXPLORER_MAP from bittensor.utils import ( - is_valid_bittensor_address_or_public_key, - unlock_key, get_explorer_url_for_network, get_transfer_fn_params, + is_valid_bittensor_address_or_public_key, + unlock_key, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -20,32 +20,31 @@ def transfer_extrinsic( wallet: "Wallet", destination: str, amount: Optional[Balance], - transfer_all: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, keep_alive: bool = True, + transfer_all: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """Transfers funds from this wallet to the destination public key address. - Args: - subtensor (bittensor.core.subtensor.Subtensor): the Subtensor object used for transfer - wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from. - destination (str): Destination public key address (ss58_address or ed25519) of recipient. - amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. `None` if transferring all. - transfer_all (bool): Whether to transfer all funds from this wallet to the destination address. - wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns - `False` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning - `True`, or returns `False` if the extrinsic fails to be finalized within the timeout. - keep_alive (bool): If set, keeps the account alive by keeping the balance above the existential deposit. - 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. + Parameters: + subtensor: the Subtensor object used for transfer + wallet: The wallet to sign the extrinsic. + destination: Destination public key address (ss58_address or ed25519) of recipient. + amount: Amount to stake as Bittensor balance. `None` if transferring all. + transfer_all: Whether to transfer all funds from this wallet to the destination address. + keep_alive: If set, keeps the account alive by keeping the balance above the existential deposit. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for - finalization / inclusion, the response is `True`, regardless of its inclusion. + bool: True if the subnet registration was successful, False otherwise. """ if amount is None and not transfer_all: logging.error("If not transferring all, `amount` must be specified.") @@ -110,6 +109,7 @@ def transfer_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 f006ec92d1..83f1c62e11 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4338,27 +4338,28 @@ def transfer( wallet: "Wallet", destination: str, amount: Optional[Balance], - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, transfer_all: bool = False, keep_alive: bool = True, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, ) -> bool: """ Transfer token of amount to destination. - Arguments: - wallet (bittensor_wallet.Wallet): Source wallet for the transfer. - destination (str): Destination address for the transfer. - amount (float): Amount of tao to transfer. - transfer_all (bool): Flag to transfer all tokens. Default is ``False``. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``True``. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is - ``False``. - keep_alive (bool): Flag to keep the connection alive. Default is ``True``. - 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. + Parameters: + wallet: Source wallet for the transfer. + destination: Destination address for the transfer. + amount: Number of tokens to transfer. `None` is transferring all. + transfer_all: Flag to transfer all tokens. Default is `False`. + keep_alive: Flag to keep the connection alive. Default is `True`. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: `True` if the transferring was successful, otherwise `False`. @@ -4371,10 +4372,11 @@ def transfer( destination=destination, amount=amount, transfer_all=transfer_all, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, keep_alive=keep_alive, period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) def transfer_stake( diff --git a/migration.md b/migration.md index 64a2ba3960..f01ab6f02e 100644 --- a/migration.md +++ b/migration.md @@ -197,4 +197,5 @@ wait_for_finalization: bool = False, - [x] `.add_stake_extrinsic` and `subtensor.add_stake` - [x] `.add_stake_multiple_extrinsic` and `subtensor.add_stake_multiple` - [x] `.start_call_extrinsic` and `subtensor.start_call` -- [x] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` \ No newline at end of file +- [x] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` +- [x] `.transfer_extrinsic` and `subtensor.transfer` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/asyncex/test_transfer.py b/tests/unit_tests/extrinsics/asyncex/test_transfer.py index a755c8c894..3992fa33bb 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_transfer.py +++ b/tests/unit_tests/extrinsics/asyncex/test_transfer.py @@ -68,6 +68,7 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result is True @@ -140,6 +141,7 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result is False diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index b4ceee33f8..b885977de8 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -67,6 +67,7 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result is True @@ -137,6 +138,7 @@ def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_inclusion=True, wait_for_finalization=True, period=None, + raise_error=False, ) assert result is False diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 662aa4e8a8..490c21bf77 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2604,6 +2604,7 @@ async def test_transfer_success(subtensor, fake_wallet, mocker): wait_for_finalization=False, keep_alive=True, period=None, + raise_error=False, ) assert result == mocked_transfer_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 71210f6b5e..7a0170a17a 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1333,6 +1333,7 @@ def test_transfer(subtensor, fake_wallet, mocker): wait_for_finalization=fake_wait_for_finalization, keep_alive=True, period=None, + raise_error=False, ) assert result == mocked_transfer_extrinsic.return_value From 232a12cb17ce7601aaf49c309c711e22c5e57e2c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 14:03:13 -0700 Subject: [PATCH 138/177] `.unstake_extrinsic` and `subtensor.unstake` + changes + tests --- bittensor/core/async_subtensor.py | 57 ++++++----- .../core/extrinsics/asyncex/unstaking.py | 94 ++++++++----------- bittensor/core/extrinsics/unstaking.py | 86 +++++++---------- bittensor/core/subtensor.py | 60 ++++++------ migration.md | 8 +- tests/e2e_tests/test_staking.py | 72 ++++++-------- .../extrinsics/asyncex/test_unstaking.py | 1 + tests/unit_tests/extrinsics/test_unstaking.py | 1 + tests/unit_tests/test_subtensor.py | 20 ++-- 9 files changed, 178 insertions(+), 221 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 875615573f..dd4bbbc706 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5622,40 +5622,39 @@ async def transfer_stake( async def unstake( self, wallet: "Wallet", - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, # TODO why is this optional? - amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - safe_staking: bool = False, + netuid: int, + hotkey_ss58: str, + amount: Balance, allow_partial_stake: bool = False, + safe_staking: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, - unstake_all: bool = False, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting individual neuron stakes within the Bittensor network. - Arguments: - wallet: The wallet associated with the neuron from which the stake is being - removed. - hotkey_ss58: The `SS58` address of the hotkey account to unstake from. + Parameters: + wallet: The wallet associated with the neuron from which the stake is being removed. netuid: The unique identifier of the subnet. - amount: The amount of alpha to unstake. If not specified, unstakes all. - wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to `False`. - safe_staking: If true, enables price safety checks to protect against fluctuating prices. The unstake will - only execute if the price change doesn't exceed the rate tolerance. Default is False. + hotkey_ss58: The ``SS58`` address of the hotkey account to unstake from. + amount: The amount of alpha to unstake. If not specified, unstakes all. Alpha amount. allow_partial_stake: If true and safe_staking is enabled, allows partial unstaking when - the full amount would exceed the price threshold. If false, the entire unstake fails if it would exceed - the threshold. Default is False. - rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum - price decrease. Only used when safe_staking is True. Default is 0.005. - period: 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. - unstake_all: If `True`, unstakes all tokens and `amount` is ignored. Default is `False` + the full amount would exceed the price tolerance. If false, the entire unstake fails if it would + exceed the tolerance. + rate_tolerance: The maximum allowed price change ratio when unstaking. For example, + 0.005 = 0.5% maximum price decrease. Only used when safe_staking is True. + safe_staking: If true, enables price safety checks to protect against fluctuating prices. The unstake + will only execute if the price change doesn't exceed the rate tolerance. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: bool: `True` if the unstaking process is successful, False otherwise. @@ -5670,13 +5669,13 @@ async def unstake( hotkey_ss58=hotkey_ss58, netuid=netuid, amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, + safe_staking=safe_staking, period=period, - unstake_all=unstake_all, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) async def unstake_all( @@ -5686,7 +5685,7 @@ async def unstake_all( netuid: int, rate_tolerance: Optional[float] = 0.005, wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, + wait_for_finalization: bool = True, period: Optional[int] = None, ) -> tuple[bool, str]: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index fcc0416dd5..f62359b3da 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -16,54 +16,44 @@ async def unstake_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, - amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - safe_staking: bool = False, + netuid: int, + hotkey_ss58: str, + amount: Balance, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + safe_staking: bool = False, period: Optional[int] = None, - unstake_all: bool = False, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Removes stake into the wallet coldkey from the specified hotkey ``uid``. + """ + Removes stake into the wallet coldkey from the specified hotkey ``uid``. - Args: - subtensor: AsyncSubtensor instance. + Parameters: + subtensor: Subtensor instance. wallet: Bittensor wallet object. - hotkey_ss58: The ``ss58`` address of the hotkey to unstake from. By default, the wallet hotkey is used. - netuid: The subnet uid to unstake from. - amount: Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns - ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, - or returns ``False`` if the extrinsic fails to be finalized within the timeout. - safe_staking: If true, enables price safety checks - allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded - rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%) + netuid: Subnet unique id. + hotkey_ss58: The ``ss58`` address of the hotkey to unstake from. + amount: Amount to stake as Bittensor balance. + allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded. + safe_staking: If true, enables price safety checks. + rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). period: 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. - unstake_all: If true, unstakes all tokens. Default is ``False``. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - A tuple containing: - - `True` and a success message if the unstake operation succeeded; - - `False` and an error message otherwise. + bool: True if the subnet registration was successful, False otherwise. """ - if amount and unstake_all: - raise ValueError("Cannot specify both `amount` and `unstake_all`.") - # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) return False - if hotkey_ss58 is None: - hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. - logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) @@ -78,24 +68,13 @@ async def unstake_extrinsic( ), ) - # Convert to bittensor.Balance - if amount is None: - # Unstake it all. - unstaking_balance = old_stake - logging.warning( - f"Didn't receive any unstaking amount. Unstaking all existing stake: [blue]{old_stake}[/blue] " - f"from hotkey: [blue]{hotkey_ss58}[/blue]" - ) - else: - unstaking_balance = amount - unstaking_balance.set_unit(netuid) + amount.set_unit(netuid) # Check enough to unstake. - stake_on_uid = old_stake - if unstaking_balance > stake_on_uid: + if amount > old_stake: logging.error( - f":cross_mark: [red]Not enough stake[/red]: [green]{stake_on_uid}[/green] to unstake: " - f"[blue]{unstaking_balance}[/blue] from hotkey: [yellow]{wallet.hotkey_str}[/yellow]" + f":cross_mark: [red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " + f"[blue]{amount}[/blue] from hotkey: [yellow]{wallet.hotkey_str}[/yellow]" ) return False @@ -103,7 +82,7 @@ async def unstake_extrinsic( call_params = { "hotkey": hotkey_ss58, "netuid": netuid, - "amount_unstaked": unstaking_balance.rao, + "amount_unstaked": amount.rao, } if safe_staking: pool = await subtensor.subnet(netuid=netuid) @@ -116,7 +95,7 @@ async def unstake_extrinsic( logging_info = ( f":satellite: [magenta]Safe Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green], " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " f"price limit: [green]{price_with_tolerance}[/green], " f"original price: [green]{base_price}[/green], " @@ -135,7 +114,7 @@ async def unstake_extrinsic( else: logging_info = ( f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green] " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " f"on [blue]{subtensor.network}[/blue]" ) call_function = "remove_stake" @@ -158,9 +137,10 @@ async def unstake_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) - if success is True: # If we successfully unstaked. + if success: # If we successfully unstaked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: return True @@ -190,14 +170,14 @@ async def unstake_extrinsic( f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" ) return True + + if safe_staking and "Custom error: 8" in message: + logging.error( + ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." + ) else: - if safe_staking and "Custom error: 8" in message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") - return False + logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + return False except SubstrateRequestException as error: logging.error( diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 8f0ce3329d..10c021ca05 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -16,54 +16,44 @@ def unstake_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, - amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - safe_staking: bool = False, + netuid: int, + hotkey_ss58: str, + amount: Balance, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + safe_staking: bool = False, period: Optional[int] = None, - unstake_all: bool = False, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Removes stake into the wallet coldkey from the specified hotkey ``uid``. + """ + Removes stake into the wallet coldkey from the specified hotkey ``uid``. - Args: + Parameters: subtensor: Subtensor instance. wallet: Bittensor wallet object. - hotkey_ss58: The ``ss58`` address of the hotkey to unstake from. By default, the wallet hotkey is used. + hotkey_ss58: The ``ss58`` address of the hotkey to unstake from. netuid: Subnet unique id. amount: Amount to stake as Bittensor balance. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns - ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, - or returns ``False`` if the extrinsic fails to be finalized within the timeout. + allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded. + rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). safe_staking: If true, enables price safety checks. - allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded - rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%) period: 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. - unstake_all: If true, unstakes all tokens. Default is ``False``. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - A tuple containing: - - `True` and a success message if the unstake operation succeeded; - - `False` and an error message otherwise. + bool: True if the subnet registration was successful, False otherwise. """ - if amount and unstake_all: - raise ValueError("Cannot specify both `amount` and `unstake_all`.") - # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) return False - if hotkey_ss58 is None: - hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. - logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) @@ -76,24 +66,14 @@ def unstake_extrinsic( block=block, ) - # Convert to bittensor.Balance - if amount is None: - # Unstake it all. - logging.warning( - f"Didn't receive any unstaking amount. Unstaking all existing stake: [blue]{old_stake}[/blue] " - f"from hotkey: [blue]{hotkey_ss58}[/blue]" - ) - unstaking_balance = old_stake - else: - unstaking_balance = amount - unstaking_balance.set_unit(netuid) + # unstaking_balance = amount + amount.set_unit(netuid) # Check enough to unstake. - stake_on_uid = old_stake - if unstaking_balance > stake_on_uid: + if amount > old_stake: logging.error( - f":cross_mark: [red]Not enough stake[/red]: [green]{stake_on_uid}[/green] to unstake: " - f"[blue]{unstaking_balance}[/blue] from hotkey: [yellow]{wallet.hotkey_str}[/yellow]" + f":cross_mark: [red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " + f"[blue]{amount}[/blue] from hotkey: [yellow]{wallet.hotkey_str}[/yellow]" ) return False @@ -101,7 +81,7 @@ def unstake_extrinsic( call_params = { "hotkey": hotkey_ss58, "netuid": netuid, - "amount_unstaked": unstaking_balance.rao, + "amount_unstaked": amount.rao, } if safe_staking: @@ -115,7 +95,7 @@ def unstake_extrinsic( logging_info = ( f":satellite: [magenta]Safe Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green], " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " f"price limit: [green]{price_with_tolerance}[/green], " f"original price: [green]{base_price}[/green], " @@ -134,7 +114,7 @@ def unstake_extrinsic( else: logging_info = ( f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{unstaking_balance}[/green] " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " f"on [blue]{subtensor.network}[/blue]" ) call_function = "remove_stake" @@ -158,6 +138,7 @@ def unstake_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) if success: # If we successfully unstaked. @@ -188,14 +169,15 @@ def unstake_extrinsic( f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" ) return True + + if safe_staking and "Custom error: 8" in message: + logging.error( + ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or" + " enable partial staking." + ) else: - if safe_staking and "Custom error: 8" in message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") - return False + logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + return False except SubstrateRequestException as error: logging.error( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 83f1c62e11..819ebd4d2a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4430,61 +4430,61 @@ def transfer_stake( def unstake( self, wallet: "Wallet", - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, # TODO why is this optional? - amount: Optional[Balance] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - safe_staking: bool = False, + netuid: int, + hotkey_ss58: str, + amount: Balance, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, + safe_staking: bool = False, period: Optional[int] = None, - unstake_all: bool = False, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting - individual neuron stakes within the Bittensor network. + individual neuron stakes within the Bittensor network. - Args: + Parameters: wallet: The wallet associated with the neuron from which the stake is being removed. - hotkey_ss58: The ``SS58`` address of the hotkey account to unstake from. netuid: The unique identifier of the subnet. + hotkey_ss58: The ``SS58`` address of the hotkey account to unstake from. amount: The amount of alpha to unstake. If not specified, unstakes all. Alpha amount. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - safe_staking: If true, enables price safety checks to protect against fluctuating prices. The unstake - will only execute if the price change doesn't exceed the rate tolerance. Default is False. - allow_partial_stake (bool): If true and safe_staking is enabled, allows partial unstaking when + allow_partial_stake: If true and safe_staking is enabled, allows partial unstaking when the full amount would exceed the price tolerance. If false, the entire unstake fails if it would - exceed the tolerance. Default is False. - rate_tolerance (float): The maximum allowed price change ratio when unstaking. For example, - 0.005 = 0.5% maximum price decrease. Only used when safe_staking is True. Default is 0.005. - 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. - unstake_all: If `True`, unstakes all tokens, and `amount` is ignored. Default is `False`. + exceed the tolerance. + rate_tolerance: The maximum allowed price change ratio when unstaking. For example, + 0.005 = 0.5% maximum price decrease. Only used when safe_staking is True. + safe_staking: If true, enables price safety checks to protect against fluctuating prices. The unstake + will only execute if the price change doesn't exceed the rate tolerance. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: bool: ``True`` if the unstaking process is successful, False otherwise. This function supports flexible stake management, allowing neurons to adjust their network participation and - potential reward accruals. When safe_staking is enabled, it provides protection against price fluctuations - during the time unstake is executed and the time it is actually processed by the chain. + potential reward accruals. When safe_staking is enabled, it provides protection against price fluctuations + during the time unstake is executed and the time it is actually processed by the chain. """ amount = check_and_convert_to_balance(amount) return unstake_extrinsic( subtensor=self, wallet=wallet, - hotkey_ss58=hotkey_ss58, netuid=netuid, + hotkey_ss58=hotkey_ss58, amount=amount, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - safe_staking=safe_staking, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, + safe_staking=safe_staking, period=period, - unstake_all=unstake_all, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) def unstake_all( @@ -4494,7 +4494,7 @@ def unstake_all( netuid: int, rate_tolerance: Optional[float] = 0.005, wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, + wait_for_finalization: bool = True, period: Optional[int] = None, ) -> tuple[bool, str]: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. diff --git a/migration.md b/migration.md index f01ab6f02e..b55b85a403 100644 --- a/migration.md +++ b/migration.md @@ -198,4 +198,10 @@ wait_for_finalization: bool = False, - [x] `.add_stake_multiple_extrinsic` and `subtensor.add_stake_multiple` - [x] `.start_call_extrinsic` and `subtensor.start_call` - [x] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` -- [x] `.transfer_extrinsic` and `subtensor.transfer` \ No newline at end of file +- [x] `.transfer_extrinsic` and `subtensor.transfer` +- [x] `.unstake_extrinsic` and `subtensor.unstake` + - Changes in `unstake_extrinsic`: + - parameter `netuid: Optional[int]` is now required -> `netuid: int` + - parameter `hotkey_ss58: Optional[str]` is now required -> `hotkey_ss58: str` + - parameter `amount: Optional[Balance]` is now required -> `amount: Balance` + - parameter `unstake_all: bool` removed (use `unstake_all_extrinsic` for unstake all stake) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 27b3468c89..6ea1ffa3dc 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -165,13 +165,19 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): ), } + stake = 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 = subtensor.staking.unstake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake, period=16, ) @@ -351,10 +357,8 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) # 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, + hotkey=bob_wallet.hotkey.ss58_address, period=16, ) assert success is True, message @@ -486,9 +490,9 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): expected_fee_paid += fee_tao success = subtensor.staking.unstake_multiple( - alice_wallet, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + wallet=alice_wallet, netuids=netuids, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], amounts=[Balance.from_tao(100) for _ in netuids], ) @@ -496,8 +500,8 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): for netuid, old_stake in zip(netuids, stakes): stake = subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=netuid, ) @@ -631,9 +635,9 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) expected_fee_paid += fee_tao success = await async_subtensor.staking.unstake_multiple( - alice_wallet, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + wallet=alice_wallet, netuids=netuids, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], amounts=[Balance.from_tao(100) for _ in netuids], ) @@ -641,8 +645,8 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) 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, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=netuid, ) @@ -793,11 +797,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 1. Strict params - should fail success = subtensor.staking.unstake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=full_stake, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, @@ -820,11 +822,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 2. Partial allowed - should succeed partially success = subtensor.staking.unstake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=current_stake, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, @@ -844,11 +844,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 3. Higher threshold - should succeed fully success = subtensor.staking.unstake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=partial_unstake, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.3, # 30% allow_partial_stake=False, @@ -995,11 +993,9 @@ async def test_safe_staking_scenarios_async( # 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, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=full_stake, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, @@ -1022,11 +1018,9 @@ async def test_safe_staking_scenarios_async( # 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, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=current_stake, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, @@ -1046,11 +1040,9 @@ async def test_safe_staking_scenarios_async( # 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, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=partial_unstake, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.3, # 30% allow_partial_stake=False, @@ -1979,8 +1971,6 @@ def test_unstaking_with_limit( 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 @@ -1990,8 +1980,6 @@ def test_unstaking_with_limit( 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. @@ -2070,8 +2058,8 @@ async def test_unstaking_with_limit_async( assert await async_subtensor.staking.add_stake( wallet=bob_wallet, - hotkey_ss58=dave_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid_2, + hotkey_ss58=dave_wallet.hotkey.ss58_address, amount=Balance.from_tao(10000), wait_for_inclusion=True, wait_for_finalization=True, @@ -2079,8 +2067,8 @@ async def test_unstaking_with_limit_async( ), 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, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(15000), wait_for_inclusion=True, wait_for_finalization=True, @@ -2100,11 +2088,9 @@ async def test_unstaking_with_limit_async( ): await async_subtensor.staking.unstake_all( wallet=bob_wallet, - hotkey=bob_stakes[0].hotkey_ss58, netuid=bob_stakes[0].netuid, + hotkey=bob_stakes[0].hotkey_ss58, rate_tolerance=rate_tolerance, - wait_for_inclusion=True, - wait_for_finalization=True, ) else: # Successful cases @@ -2112,11 +2098,9 @@ async def test_unstaking_with_limit_async( assert ( await async_subtensor.staking.unstake_all( wallet=bob_wallet, - hotkey=si.hotkey_ss58, netuid=si.netuid, + hotkey=si.hotkey_ss58, rate_tolerance=rate_tolerance, - wait_for_inclusion=True, - wait_for_finalization=True, ) )[0] diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 24857e0261..9be3be85fa 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -59,6 +59,7 @@ async def test_unstake_extrinsic(fake_wallet, mocker): nonce_key="coldkeypub", use_nonce=True, period=None, + raise_error=False, ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 69e020854f..dbbbc98157 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -55,6 +55,7 @@ def test_unstake_extrinsic(fake_wallet, mocker): nonce_key="coldkeypub", use_nonce=True, period=None, + raise_error=False, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 7a0170a17a..af200dabdd 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2832,6 +2832,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): """Test unstake operation is successful.""" # Preps fake_hotkey_ss58 = "hotkey_1" + fake_netuid = 1 fake_amount = 10.0 mock_unstake_extrinsic = mocker.patch.object(subtensor_module, "unstake_extrinsic") @@ -2839,6 +2840,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): # Call result = subtensor.unstake( wallet=fake_wallet, + netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, amount=fake_amount, wait_for_inclusion=True, @@ -2852,16 +2854,16 @@ def test_unstake_success(mocker, subtensor, fake_wallet): mock_unstake_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, + netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, - netuid=None, amount=Balance.from_rao(fake_amount), - wait_for_inclusion=True, - wait_for_finalization=False, safe_staking=False, allow_partial_stake=False, rate_tolerance=0.005, period=None, - unstake_all=False, + wait_for_inclusion=True, + wait_for_finalization=False, + raise_error=False, ) assert result == mock_unstake_extrinsic.return_value @@ -2870,6 +2872,7 @@ def test_unstake_with_safe_staking(mocker, subtensor, fake_wallet): """Test unstake with safe staking parameters enabled.""" fake_hotkey_ss58 = "hotkey_1" fake_amount = 10.0 + fake_netuid = 14 fake_rate_tolerance = 0.01 # 1% threshold mock_unstake_extrinsic = mocker.patch.object(subtensor_module, "unstake_extrinsic") @@ -2877,6 +2880,7 @@ def test_unstake_with_safe_staking(mocker, subtensor, fake_wallet): # Call result = subtensor.unstake( wallet=fake_wallet, + netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, amount=fake_amount, wait_for_inclusion=True, @@ -2890,16 +2894,16 @@ def test_unstake_with_safe_staking(mocker, subtensor, fake_wallet): mock_unstake_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, + netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, - netuid=None, amount=Balance.from_rao(fake_amount), - wait_for_inclusion=True, - wait_for_finalization=False, safe_staking=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, period=None, - unstake_all=False, + raise_error=False, + wait_for_inclusion=True, + wait_for_finalization=False, ) assert result == mock_unstake_extrinsic.return_value From 3bc569e015780dce0f697f5757096daba3d3fc31 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 14:18:32 -0700 Subject: [PATCH 139/177] `.unstake_all_extrinsic` and `subtensor.unstake_all` + tests --- bittensor/core/async_subtensor.py | 19 +++++++++++-------- .../core/extrinsics/asyncex/unstaking.py | 15 +++++++++------ bittensor/core/extrinsics/unstaking.py | 15 +++++++++------ bittensor/core/subtensor.py | 19 +++++++++++-------- migration.md | 1 + .../extrinsics/asyncex/test_unstaking.py | 3 ++- tests/unit_tests/extrinsics/test_unstaking.py | 3 ++- tests/unit_tests/test_async_subtensor.py | 3 ++- tests/unit_tests/test_subtensor.py | 3 ++- 9 files changed, 49 insertions(+), 32 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index dd4bbbc706..5d52fbf184 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5684,23 +5684,25 @@ async def unstake_all( hotkey: str, netuid: int, rate_tolerance: Optional[float] = 0.005, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> tuple[bool, str]: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. - Arguments: + Parameters: wallet: The wallet of the stake owner. hotkey: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. Default is 0.005. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is `False`. - period: 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. Default is `None`. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: tuple[bool, str]: @@ -5757,9 +5759,10 @@ async def unstake_all( hotkey=hotkey, netuid=netuid, rate_tolerance=rate_tolerance, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) async def unstake_multiple( diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index f62359b3da..872d18e942 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -192,24 +192,26 @@ async def unstake_all_extrinsic( hotkey: str, netuid: int, rate_tolerance: Optional[float] = 0.005, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. - Arguments: + Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. hotkey: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. Default is `0.005`. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is `False`. period: 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. Default is `None`. + think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: tuple[bool, str]: @@ -248,6 +250,7 @@ async def unstake_all_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 10c021ca05..377791914c 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -192,24 +192,26 @@ def unstake_all_extrinsic( hotkey: str, netuid: int, rate_tolerance: Optional[float] = 0.005, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> tuple[bool, str]: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. - Arguments: + Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. hotkey: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. Default is `0.005`. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is `False`. period: 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. Default is `None`. + think of it as an expiration date for the transaction. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: tuple[bool, str]: @@ -247,6 +249,7 @@ def unstake_all_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) return success, message diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 819ebd4d2a..5169a0f475 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4493,23 +4493,25 @@ def unstake_all( hotkey: str, netuid: int, rate_tolerance: Optional[float] = 0.005, + period: Optional[int] = None, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - period: Optional[int] = None, ) -> tuple[bool, str]: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. - Arguments: + Parameters: wallet: The wallet of the stake owner. hotkey: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. If not passed (None), then unstaking goes without price limit. Default is 0.005. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `True`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is `False`. - period: 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. Default is `None`. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: tuple[bool, str]: @@ -4565,9 +4567,10 @@ def unstake_all( hotkey=hotkey, netuid=netuid, rate_tolerance=rate_tolerance, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) def unstake_multiple( diff --git a/migration.md b/migration.md index b55b85a403..14890cad68 100644 --- a/migration.md +++ b/migration.md @@ -205,3 +205,4 @@ wait_for_finalization: bool = False, - parameter `hotkey_ss58: Optional[str]` is now required -> `hotkey_ss58: str` - parameter `amount: Optional[Balance]` is now required -> `amount: Balance` - parameter `unstake_all: bool` removed (use `unstake_all_extrinsic` for unstake all stake) +- [x] `.unstake_all_extrinsic` and `subtensor.unstake_all` diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 9be3be85fa..5e7dd01df4 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -101,11 +101,12 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): call=fake_substrate.compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, + raise_error=False, ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index dbbbc98157..783f377b94 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -96,11 +96,12 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): call=fake_subtensor.substrate.compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, + raise_error=False, ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 490c21bf77..57c94ed227 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3479,8 +3479,9 @@ async def test_unstake_all(subtensor, fake_wallet, mocker): netuid=1, rate_tolerance=0.005, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == fake_unstake_all_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index af200dabdd..ce55dbabad 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3708,8 +3708,9 @@ def test_unstake_all(subtensor, fake_wallet, mocker): netuid=1, rate_tolerance=0.005, wait_for_inclusion=True, - wait_for_finalization=False, + wait_for_finalization=True, period=None, + raise_error=False, ) assert result == fake_unstake_all_extrinsic.return_value From 80d21a2847c8cd2577cd832ddf18388ac2d30bb5 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:00:15 -0700 Subject: [PATCH 140/177] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` + tests --- bittensor/core/async_subtensor.py | 31 +++++++------ .../core/extrinsics/asyncex/unstaking.py | 33 +++++++------- bittensor/core/extrinsics/unstaking.py | 33 +++++++------- bittensor/core/subtensor.py | 43 ++++++++++--------- migration.md | 1 + .../extrinsics/asyncex/test_unstaking.py | 1 + tests/unit_tests/extrinsics/test_unstaking.py | 1 + tests/unit_tests/test_subtensor.py | 1 + 8 files changed, 75 insertions(+), 69 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5d52fbf184..4adf2db840 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5768,29 +5768,31 @@ async def unstake_all( async def unstake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: list[int], + hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, unstake_all: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Performs batch unstaking from multiple hotkey accounts, allowing a neuron to reduce its staked amounts efficiently. This function is useful for managing the distribution of stakes across multiple neurons. - Arguments: + Parameters: wallet: The wallet linked to the coldkey from which the stakes are being withdrawn. - hotkey_ss58s: A list of hotkey `SS58` addresses to unstake from. netuids: Subnets unique IDs. + hotkey_ss58s: A list of hotkey `SS58` addresses to unstake from. amounts: The amounts of TAO to unstake from each hotkey. If not provided, unstakes all. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. - period: 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. unstake_all: If true, unstakes all tokens. Default is `False`. If `True` amounts are ignored. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: bool: `True` if the batch unstaking is successful, False otherwise. @@ -5801,13 +5803,14 @@ async def unstake_multiple( return await unstake_multiple_extrinsic( subtensor=self, wallet=wallet, - hotkey_ss58s=hotkey_ss58s, netuids=netuids, + hotkey_ss58s=hotkey_ss58s, amounts=amounts, + unstake_all=unstake_all, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, - unstake_all=unstake_all, ) diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 872d18e942..609349ec07 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -257,36 +257,34 @@ async def unstake_all_extrinsic( async def unstake_multiple_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: list[int], + hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, unstake_all: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. + """ + Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. - Args: - subtensor: Subtensor instance. + Parameters: + subtensor: AsyncSubtensor instance. wallet: The wallet with the coldkey to unstake to. + netuids: List of subnets unique IDs to unstake from. hotkey_ss58s: List of hotkeys to unstake from. - netuids: List of netuids to unstake from. amounts: List of amounts to unstake. If ``None``, unstake all. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning - ``True``, or returns ``False`` if the extrinsic fails to be finalized within the timeout. + unstake_all: If true, unstakes all tokens. Default is ``False``. period: 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. - unstake_all: If true, unstakes all tokens. Default is ``False``. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - A tuple containing: - - `True` and a success message if the unstake operation succeeded; - - `False` and an error message otherwise. + bool: True if the subnet registration was successful, False otherwise. """ if amounts and unstake_all: raise ValueError("Cannot specify both `amounts` and `unstake_all`.") @@ -394,6 +392,7 @@ async def unstake_multiple_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) if staking_response is True: # If we successfully unstaked. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 377791914c..fff7cdd693 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -258,36 +258,34 @@ def unstake_all_extrinsic( def unstake_multiple_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: list[int], + hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, unstake_all: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: - """Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. + """ + Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. - Args: + Parameters: subtensor: Subtensor instance. wallet: The wallet with the coldkey to unstake to. - hotkey_ss58s: List of hotkeys to unstake from. netuids: List of subnets unique IDs to unstake from. + hotkey_ss58s: List of hotkeys to unstake from. amounts: List of amounts to unstake. If ``None``, unstake all. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or - returns ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, - or returns ``False`` if the extrinsic fails to be finalized within the timeout. + unstake_all: If true, unstakes all tokens. Default is ``False``. period: 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. - unstake_all: If true, unstakes all tokens. Default is ``False``. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - A tuple containing: - - `True` and a success message if the unstake operation succeeded; - - `False` and an error message otherwise. + bool: True if the subnet registration was successful, False otherwise. """ if amounts and unstake_all: raise ValueError("Cannot specify both `amounts` and `unstake_all`.") @@ -388,9 +386,10 @@ def unstake_multiple_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) - if staking_response is True: # If we successfully unstaked. + if staking_response: # If we successfully unstaked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 5169a0f475..dfae544119 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4576,46 +4576,47 @@ def unstake_all( def unstake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: list[int], + hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, - period: Optional[int] = None, unstake_all: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> bool: """ Performs batch unstaking from multiple hotkey accounts, allowing a neuron to reduce its staked amounts efficiently. This function is useful for managing the distribution of stakes across multiple neurons. - Args: - wallet: The wallet linked to the coldkey from which the stakes are being - withdrawn. - hotkey_ss58s (List[str]): A list of hotkey ``SS58`` addresses to unstake from. - netuids (List[int]): The list of subnet uids. - amounts (List[Balance]): The amounts of TAO to unstake from each hotkey. If not provided, - unstakes all available stakes. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. - 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. - unstake_all: If `True`, unstakes all tokens, and `amounts` is ignored. Default is `False`. + Parameters: + wallet: The wallet linked to the coldkey from which the stakes are being withdrawn. + netuids: Subnets unique IDs. + hotkey_ss58s: A list of hotkey `SS58` addresses to unstake from. + amounts: The amounts of TAO to unstake from each hotkey. If not provided, unstakes all. + unstake_all: If true, unstakes all tokens. Default is `False`. If `True` amounts are ignored. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: bool: ``True`` if the batch unstaking is successful, False otherwise. This function allows for strategic reallocation or withdrawal of stakes, aligning with the dynamic stake - management aspect of the Bittensor network. + management aspect of the Bittensor network. """ return unstake_multiple_extrinsic( subtensor=self, wallet=wallet, - hotkey_ss58s=hotkey_ss58s, netuids=netuids, + hotkey_ss58s=hotkey_ss58s, amounts=amounts, + unstake_all=unstake_all, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, - unstake_all=unstake_all, ) diff --git a/migration.md b/migration.md index 14890cad68..eea33cf0ba 100644 --- a/migration.md +++ b/migration.md @@ -206,3 +206,4 @@ wait_for_finalization: bool = False, - parameter `amount: Optional[Balance]` is now required -> `amount: Balance` - parameter `unstake_all: bool` removed (use `unstake_all_extrinsic` for unstake all stake) - [x] `.unstake_all_extrinsic` and `subtensor.unstake_all` +- [x] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 5e7dd01df4..2f70f9bc68 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -179,4 +179,5 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): nonce_key="coldkeypub", use_nonce=True, period=None, + raise_error=False, ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 783f377b94..f4719cdbb2 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -173,4 +173,5 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): nonce_key="coldkeypub", use_nonce=True, period=None, + raise_error=False, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index ce55dbabad..56b18759a5 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3030,6 +3030,7 @@ def test_unstake_multiple_success(mocker, subtensor, fake_wallet): wait_for_finalization=False, period=None, unstake_all=False, + raise_error=False, ) assert result == mock_unstake_multiple_extrinsic.return_value From 1528bbc678f1292ca07d512a6ae0b9f5b4514dbd Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:12:36 -0700 Subject: [PATCH 141/177] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` + tests --- bittensor/core/async_subtensor.py | 31 ++++++++------- bittensor/core/extrinsics/asyncex/weights.py | 14 +++---- bittensor/core/extrinsics/weights.py | 14 +++---- bittensor/core/subtensor.py | 42 ++++++++++---------- migration.md | 1 + 5 files changed, 52 insertions(+), 50 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4adf2db840..74de1dc550 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4488,29 +4488,30 @@ async def commit_weights( uids: Union[NDArray[np.int64], list], weights: Union[NDArray[np.int64], list], version_key: int = version_as_int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, + raise_error: bool = True, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: """ Commits a hash of the subnet validator's weight vector to the Bittensor blockchain using the provided wallet. This action serves as a commitment or snapshot of the validator's current weight distribution. - Arguments: - wallet: The wallet associated with the subnet validator committing the weights. + Parameters: + wallet: The wallet associated with the neuron committing the weights. netuid: The unique identifier of the subnet. salt: list of randomly generated integers as salt to generated weighted hash. - uids: NumPy array of subnet miner neuron UIDs for which weights are being committed. - weights: of weight values corresponding toon_key - version_key: Integer representation of version key for compatibility with the network. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is - `False`. - max_retries: The number of maximum attempts to commit weights. Default is `5`. - period: 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. + uids: NumPy array of neuron UIDs for which weights are being committed. + weights: NumPy array of weight values corresponding to each UID. + version_key: Version key for compatibility with the network. + max_retries: The number of maximum attempts to commit weights. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: tuple[bool, str]: @@ -4553,7 +4554,7 @@ async def commit_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, - raise_error=True, + raise_error=raise_error, ) if success: break diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 8717bf638d..d231db50af 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -24,26 +24,26 @@ async def commit_weights_extrinsic( wallet: "Wallet", netuid: int, commit_hash: str, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. This function is a wrapper around the `do_commit_weights` method. - Args: + Parameters: subtensor: The subtensor instance used for blockchain interaction. wallet: The wallet associated with the neuron committing the weights. netuid: The unique identifier of the subnet. commit_hash: The hash of the neuron's weights to be committed. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. period: 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. + raise_error: Whether to raise an error if the transaction fails. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: tuple[bool, str]: @@ -51,7 +51,7 @@ async def commit_weights_extrinsic( `msg` is a string value describing the success or potential error. This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper - error handling and user interaction when required. + error handling and user interaction when required. """ call = await subtensor.substrate.compose_call( call_module="SubtensorModule", diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index fec8e36b70..9de3949cb2 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -24,26 +24,26 @@ def commit_weights_extrinsic( wallet: "Wallet", netuid: int, commit_hash: str, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. This function is a wrapper around the `do_commit_weights` method. - Args: + Parameters: subtensor: The subtensor instance used for blockchain interaction. wallet: The wallet associated with the neuron committing the weights. netuid: The unique identifier of the subnet. commit_hash: The hash of the neuron's weights to be committed. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. period: 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. + raise_error: Whether to raise an error if the transaction fails. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: tuple[bool, str]: @@ -51,7 +51,7 @@ def commit_weights_extrinsic( `msg` is a string value describing the success or potential error. This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper - error handling and user interaction when required. + error handling and user interaction when required. """ call = subtensor.substrate.compose_call( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index dfae544119..85a8e1f06d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3328,38 +3328,38 @@ def commit_weights( uids: Union[NDArray[np.int64], list], weights: Union[NDArray[np.int64], list], version_key: int = version_as_int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, max_retries: int = 5, period: Optional[int] = 16, + raise_error: bool = True, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. This action serves as a commitment or snapshot of the neuron's current weight distribution. - Arguments: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. - netuid (int): The unique identifier of the subnet. - salt (list[int]): list of randomly generated integers as salt to generated weighted hash. - uids (np.ndarray): NumPy array of neuron UIDs for which weights are being committed. - weights (np.ndarray): NumPy array of weight values corresponding to each UID. - version_key (int): Version key for compatibility with the network. Default is ``int representation of - a Bittensor version.``. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``False``. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is - ``False``. - max_retries (int): The number of maximum attempts to commit weights. Default is ``5``. - 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. + Parameters: + wallet: The wallet associated with the neuron committing the weights. + netuid: The unique identifier of the subnet. + salt: list of randomly generated integers as salt to generated weighted hash. + uids: NumPy array of neuron UIDs for which weights are being committed. + weights: NumPy array of weight values corresponding to each UID. + version_key: Version key for compatibility with the network. + max_retries: The number of maximum attempts to commit weights. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: tuple[bool, str]: `True` if the weight commitment is successful, False otherwise. `msg` is a string value describing the success or potential error. - This function allows neurons to create a tamper-proof record of their weight distribution at a specific point - in time, enhancing transparency and accountability within the Bittensor network. + This function allows neurons to create a tamper-proof record of their weight distribution at a specific point in + time, enhancing transparency and accountability within the Bittensor network. """ retries = 0 success = False @@ -3388,10 +3388,10 @@ def commit_weights( wallet=wallet, netuid=netuid, commit_hash=commit_hash, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, - raise_error=True, ) if success: break diff --git a/migration.md b/migration.md index eea33cf0ba..dca67d3d77 100644 --- a/migration.md +++ b/migration.md @@ -207,3 +207,4 @@ wait_for_finalization: bool = False, - parameter `unstake_all: bool` removed (use `unstake_all_extrinsic` for unstake all stake) - [x] `.unstake_all_extrinsic` and `subtensor.unstake_all` - [x] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` +- [x] `.commit_weights_extrinsic` and `subtensor.commit_weights` From f1eb2d1a4a6d68f2ef6fb2d9a21f272782b3593b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:27:47 -0700 Subject: [PATCH 142/177] `.reveal_weights_extrinsic` and `subtensor.reveal_weights` --- bittensor/core/async_subtensor.py | 28 ++++++------ bittensor/core/extrinsics/asyncex/weights.py | 12 ++--- bittensor/core/extrinsics/weights.py | 12 ++--- bittensor/core/subtensor.py | 46 ++++++++++---------- migration.md | 1 + 5 files changed, 51 insertions(+), 48 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 74de1dc550..f0223537ca 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4841,32 +4841,32 @@ async def reveal_weights( uids: Union[NDArray[np.int64], list], weights: Union[NDArray[np.int64], list], salt: Union[NDArray[np.int64], list], + max_retries: int = 5, version_key: int = version_as_int, + period: Optional[int] = 16, + raise_error: bool = True, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - max_retries: int = 5, - period: Optional[int] = None, ) -> tuple[bool, str]: """ Reveals the weight vector for a specific subnet on the Bittensor blockchain using the provided wallet. This action serves as a revelation of the subnet validator's previously committed weight distribution as part of the commit-reveal mechanism. - Arguments: + Parameters: wallet: The wallet associated with the subnet validator revealing the weights. netuid: unique identifier of the subnet. uids: NumPy array of subnet miner neuron UIDs for which weights are being revealed. weights: NumPy array of weight values corresponding to each UID. salt: NumPy array of salt values - version_key: Version key for compatibility with the network. Default is `int representation of - the Bittensor version`. - wait_for_inclusion: Waits for the transaction to be included in a block. Default is `False`. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Default is - `False`. - max_retries: The number of maximum attempts to reveal weights. Default is `5`. - period: 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. + max_retries: The number of maximum attempts to reveal weights. + version_key: Version key for compatibility with the network. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: Tuple[bool, str]: @@ -4891,10 +4891,10 @@ async def reveal_weights( weights=list(weights), salt=list(salt), version_key=version_key, + period=period, + raise_error=raise_error, 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 d231db50af..a6586a784f 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -88,16 +88,16 @@ async def reveal_weights_extrinsic( weights: list[int], salt: list[int], version_key: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. This function is a wrapper around the `_do_reveal_weights` method. - Args: + Parameters: subtensor: The subtensor instance used for blockchain interaction. wallet: The wallet associated with the neuron revealing the weights. netuid: The unique identifier of the subnet. @@ -105,12 +105,12 @@ async def reveal_weights_extrinsic( weights: List of weight values corresponding to each UID. salt: List of salt values corresponding to the hash function. version_key: Version key for compatibility with the network. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. period: 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. + raise_error: Whether to raise an error if the transaction fails. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: tuple[bool, str]: diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 9de3949cb2..46f660a514 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -89,16 +89,16 @@ def reveal_weights_extrinsic( weights: list[int], salt: list[int], version_key: int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. This function is a wrapper around the `_do_reveal_weights` method. - Args: + Parameters: subtensor: The subtensor instance used for blockchain interaction. wallet: The wallet associated with the neuron revealing the weights. netuid: The unique identifier of the subnet. @@ -106,12 +106,12 @@ def reveal_weights_extrinsic( weights: List of weight values corresponding to each UID. salt: List of salt values corresponding to the hash function. version_key: Version key for compatibility with the network. - wait_for_inclusion: Waits for the transaction to be included in a block. - wait_for_finalization: Waits for the transaction to be finalized on the blockchain. period: 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. + raise_error: Whether to raise an error if the transaction fails. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: tuple[bool, str]: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 85a8e1f06d..47bcc61715 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3678,38 +3678,40 @@ def reveal_weights( uids: Union[NDArray[np.int64], list], weights: Union[NDArray[np.int64], list], salt: Union[NDArray[np.int64], list], + max_retries: int = 5, version_key: int = version_as_int, + period: Optional[int] = 16, + raise_error: bool = True, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - max_retries: int = 5, - period: Optional[int] = 16, ) -> tuple[bool, str]: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. This action serves as a revelation of the neuron's previously committed weight distribution. - Args: - wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. - netuid (int): The unique identifier of the subnet. - uids (np.ndarray): NumPy array of neuron UIDs for which weights are being revealed. - weights (np.ndarray): NumPy array of weight values corresponding to each UID. - salt (np.ndarray): NumPy array of salt values corresponding to the hash function. - version_key (int): Version key for compatibility with the network. Default is ``int representation of - the Bittensor version``. - wait_for_inclusion (bool): Waits for the transaction to be included in a block. Default is ``False``. - wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. Default is - ``False``. - max_retries (int): The number of maximum attempts to reveal weights. Default is ``5``. - 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. + Parameters: + wallet: The wallet associated with the subnet validator revealing the weights. + netuid: unique identifier of the subnet. + uids: NumPy array of subnet miner neuron UIDs for which weights are being revealed. + weights: NumPy array of weight values corresponding to each UID. + salt: NumPy array of salt values + max_retries: The number of maximum attempts to reveal weights. + version_key: Version key for compatibility with the network. + period: 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 a relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: Whether to wait for the extrinsic to be included in a block. + wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - tuple[bool, str]: ``True`` if the weight revelation is successful, False otherwise. And `msg`, a string - value describing the success or potential error. + Tuple[bool, str]: + - True and a success message if the extrinsic is successfully submitted or processed. + - False and an error message if the submission fails or the wallet cannot be unlocked. + + This function allows subnet validators to reveal their previously committed weight vector. - This function allows neurons to reveal their previously committed weight distribution, ensuring transparency - and accountability within the Bittensor network. + See also: , """ retries = 0 success = False @@ -3728,7 +3730,7 @@ def reveal_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, - raise_error=True, + raise_error=raise_error, ) if success: break diff --git a/migration.md b/migration.md index dca67d3d77..bdd9ec375a 100644 --- a/migration.md +++ b/migration.md @@ -208,3 +208,4 @@ wait_for_finalization: bool = False, - [x] `.unstake_all_extrinsic` and `subtensor.unstake_all` - [x] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` - [x] `.commit_weights_extrinsic` and `subtensor.commit_weights` +- [x] `.reveal_weights_extrinsic` and `subtensor.reveal_weights` From 12c22b03b202d8b486f63c877e34399ab97c01d8 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:30:38 -0700 Subject: [PATCH 143/177] update migration.md --- migration.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/migration.md b/migration.md index bdd9ec375a..1ebdcf942e 100644 --- a/migration.md +++ b/migration.md @@ -181,9 +181,9 @@ wait_for_finalization: bool = False, - [x] `.transfer_stake_extrinsic` and `subtensor.transfer_stake` - [x] `.swap_stake_extrinsic` and `subtensor.swap_stake` - [x] `.move_stake_extrinsic` and `subtensor.move_stake` -- [x] `.move_stake_extrinsic` has renamed parameters: - - `origin_hotkey` to `origin_hotkey_ss58` - - `destination_hotkey` to `destination_hotkey_ss58` + - Changes in `move_stake_extrinsic` and `subtensor.move_stake`: + - parameter `origin_hotkey` renamed to `origin_hotkey_ss58` + - parameter `destination_hotkey` renamed to `destination_hotkey_ss58` - [x] `.burned_register_extrinsic` and `subtensor.burned_register` - [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` - [x] `.register_extrinsic` and `subtensor.register` @@ -200,7 +200,7 @@ wait_for_finalization: bool = False, - [x] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` - [x] `.transfer_extrinsic` and `subtensor.transfer` - [x] `.unstake_extrinsic` and `subtensor.unstake` - - Changes in `unstake_extrinsic`: + - Changes in `unstake_extrinsic` and `subtensor.unstake`: - parameter `netuid: Optional[int]` is now required -> `netuid: int` - parameter `hotkey_ss58: Optional[str]` is now required -> `hotkey_ss58: str` - parameter `amount: Optional[Balance]` is now required -> `amount: Balance` From 4098506017f706833443bdfdfcb92de9c7adf098 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:45:10 -0700 Subject: [PATCH 144/177] `.set_weights_extrinsic` and `subtensor.set_weights` --- bittensor/core/async_subtensor.py | 42 ++++--- bittensor/core/extrinsics/asyncex/weights.py | 18 +-- bittensor/core/extrinsics/commit_reveal.py | 103 +++++++++--------- bittensor/core/extrinsics/weights.py | 17 ++- bittensor/core/subtensor.py | 37 ++++--- migration.md | 1 + .../extrinsics/test_commit_reveal.py | 21 ++-- tests/unit_tests/test_async_subtensor.py | 1 + tests/unit_tests/test_subtensor.py | 3 +- 9 files changed, 124 insertions(+), 119 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f0223537ca..259eff3e11 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5168,7 +5168,7 @@ async def set_weights( max_retries: int = 5, version_key: int = version_as_int, period: Optional[int] = 8, - raise_error: bool = False, + raise_error: bool = True, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ): @@ -5238,23 +5238,28 @@ async def _blocks_weight_limit() -> bool: and await _blocks_weight_limit() ): logging.info( - f"Committing weights for subnet #{netuid}. Attempt {retries + 1} of {max_retries}." + f"Committing weights for subnet [blue]{netuid}[/blue]. " + f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - success, message = await commit_reveal_extrinsic( - subtensor=self, - wallet=wallet, - netuid=netuid, - uids=uids, - weights=weights, - block_time=block_time, - commit_reveal_version=commit_reveal_version, - version_key=version_key, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - retries += 1 + try: + success, message = await commit_reveal_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + uids=uids, + weights=weights, + block_time=block_time, + commit_reveal_version=commit_reveal_version, + version_key=version_key, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + except Exception as e: + logging.error(f"Error setting weights: {e}") + retries += 1 + return success, message else: # go with classic `set weights extrinsic` @@ -5276,9 +5281,10 @@ async def _blocks_weight_limit() -> bool: uids=uids, weights=weights, version_key=version_key, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) except Exception as e: logging.error(f"Error setting weights: {e}") diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index a6586a784f..53aa650f2d 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -157,28 +157,27 @@ async def set_weights_extrinsic( uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], version_key: int = version_as_int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, period: Optional[int] = 8, raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: - """Sets the given weights and values on chain for a given wallet hotkey account. + """ + Sets the given weights and values on chain for a given wallet hotkey account. - Args: + Parameters: subtensor: Bittensor subtensor object. wallet: Bittensor wallet object. netuid: The ``netuid`` of the subnet to set weights for. uids: The ``uint64`` uids of destination neurons. weights: The weights to set. These must be ``float``s and correspond to the passed ``uid``s. version_key: The version key of the validator. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns - ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, - or returns ``False`` if the extrinsic fails to be finalized within the timeout. period: 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. + raise_error: Whether to raise an error if the transaction fails. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: tuple[bool, str]: @@ -223,4 +222,5 @@ async def set_weights_extrinsic( logging.info("Successfully set weights and Finalized.") else: logging.error(f"{get_function_name}: {message}") + return success, message diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index 997b13b410..d0997605b4 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -54,63 +54,58 @@ def commit_reveal_extrinsic( - True and a success message if the extrinsic is successfully submitted or processed. - False and an error message if the submission fails or the wallet cannot be unlocked. """ - try: - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - current_block = subtensor.get_current_block() - subnet_hyperparameters = subtensor.get_subnet_hyperparameters( - netuid, block=current_block - ) - tempo = subnet_hyperparameters.tempo - subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period + current_block = subtensor.get_current_block() + subnet_hyperparameters = subtensor.get_subnet_hyperparameters( + netuid, block=current_block + ) + tempo = subnet_hyperparameters.tempo + subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period - # Encrypt `commit_hash` with t-lock and `get reveal_round` - commit_for_reveal, reveal_round = get_encrypted_commit( - uids=uids, - weights=weights, - version_key=version_key, - tempo=tempo, - current_block=current_block, - netuid=netuid, - subnet_reveal_period_epochs=subnet_reveal_period_epochs, - block_time=block_time, - hotkey=wallet.hotkey.public_key, - ) + # Encrypt `commit_hash` with t-lock and `get reveal_round` + commit_for_reveal, reveal_round = get_encrypted_commit( + uids=uids, + weights=weights, + version_key=version_key, + tempo=tempo, + current_block=current_block, + netuid=netuid, + subnet_reveal_period_epochs=subnet_reveal_period_epochs, + block_time=block_time, + hotkey=wallet.hotkey.public_key, + ) - logging.info( - f"Committing weights hash [blue]{commit_for_reveal.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with " - f"reveal round [blue]{reveal_round}[/blue]..." - ) + logging.info( + f"Committing weights hash [blue]{commit_for_reveal.hex()}[/blue] for subnet #[blue]{netuid}[/blue] with " + f"reveal round [blue]{reveal_round}[/blue]..." + ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_timelocked_weights", - call_params={ - "netuid": netuid, - "commit": commit_for_reveal, - "reveal_round": reveal_round, - "commit_reveal_version": commit_reveal_version, - }, - ) - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - raise_error=raise_error, - ) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_timelocked_weights", + call_params={ + "netuid": netuid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + }, + ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with="hotkey", + period=period, + raise_error=raise_error, + ) - if not success: - logging.error(message) - return False, message + if not success: + logging.error(message) + return False, message - logging.success( - f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." - ) - return True, f"reveal_round:{reveal_round}" - - except Exception as e: - logging.error(f":cross_mark: [red]Failed. Error:[/red] {e}") - return False, str(e) + logging.success( + f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." + ) + return True, f"reveal_round:{reveal_round}" diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 46f660a514..0a3aa8e264 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -160,28 +160,27 @@ def set_weights_extrinsic( uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], version_key: int = version_as_int, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, period: Optional[int] = 8, raise_error: bool = False, + wait_for_inclusion: bool = False, + wait_for_finalization: bool = False, ) -> tuple[bool, str]: - """Sets the given weights and values on a chain for a wallet hotkey account. + """ + Sets the given weights and values on a chain for a wallet hotkey account. - Args: + Parameters: subtensor: Bittensor subtensor object. wallet: Bittensor wallet object. netuid: The ``netuid`` of the subnet to set weights for. uids: The ``uint64`` uids of destination neurons. weights: The weights to set. These must be ``float``s and correspond to the passed ``uid``s. version_key: The version key of the validator. - wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning ``True``, or returns - ``False`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning ``True``, - or returns ``False`` if the extrinsic fails to be finalized within the timeout. period: 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. + raise_error: Whether to raise an error if the transaction fails. + wait_for_inclusion: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: tuple[bool, str]: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 47bcc61715..7924b4b584 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3990,7 +3990,7 @@ def set_weights( max_retries: int = 5, version_key: int = version_as_int, period: Optional[int] = 8, - raise_error: bool = False, + raise_error: bool = True, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> tuple[bool, str]: @@ -4052,21 +4052,25 @@ def _blocks_weight_limit() -> bool: f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - success, message = commit_reveal_extrinsic( - subtensor=self, - wallet=wallet, - netuid=netuid, - uids=uids, - weights=weights, - block_time=block_time, - commit_reveal_version=commit_reveal_version, - version_key=version_key, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) + try: + success, message = commit_reveal_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + uids=uids, + weights=weights, + block_time=block_time, + commit_reveal_version=commit_reveal_version, + version_key=version_key, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + except Exception as e: + logging.error(f"Error setting weights: {e}") retries += 1 + return success, message else: # go with classic `set_weights_extrinsic` @@ -4084,9 +4088,10 @@ def _blocks_weight_limit() -> bool: uids=uids, weights=weights, version_key=version_key, + period=period, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - period=period, ) except Exception as e: logging.error(f"Error setting weights: {e}") diff --git a/migration.md b/migration.md index 1ebdcf942e..09004c4cf8 100644 --- a/migration.md +++ b/migration.md @@ -209,3 +209,4 @@ wait_for_finalization: bool = False, - [x] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` - [x] `.commit_weights_extrinsic` and `subtensor.commit_weights` - [x] `.reveal_weights_extrinsic` and `subtensor.reveal_weights` +- [x] `.set_weights_extrinsic` and `subtensor.set_weights` \ No newline at end of file diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index e9490bcbc4..92dc926f51 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -249,15 +249,12 @@ def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor, fake_wallet): side_effect=Exception("Test Error"), ) - # Call - success, message = commit_reveal.commit_reveal_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - ) - - # Asserts - assert success is False - assert "Test Error" in message + # Call + Asserts + with pytest.raises(Exception): + commit_reveal.commit_reveal_extrinsic( + subtensor=subtensor, + wallet=fake_wallet, + netuid=fake_netuid, + uids=fake_uids, + weights=fake_weights, + ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 57c94ed227..28decac35a 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2805,6 +2805,7 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): wait_for_inclusion=True, weights=fake_weights, period=8, + raise_error=True, ) mocked_weights_rate_limit.assert_called_once_with(fake_netuid) assert result is True diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 56b18759a5..b0d7af970d 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1202,6 +1202,7 @@ def test_set_weights(subtensor, mocker, fake_wallet): wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, period=8, + raise_error=True, ) assert result == expected_result @@ -3081,7 +3082,7 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): wait_for_finalization=fake_wait_for_finalization, block_time=12.0, period=8, - raise_error=False, + raise_error=True, ) assert result == mocked_commit_reveal_v3_extrinsic.return_value From f3d663361cedaa36b7b9508dd4adfea7f4360ffc Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 16:33:35 -0700 Subject: [PATCH 145/177] parameter `safe_staking: bool` renamed to `safe_unstaking: bool` for `.unstake_extrinsic` and `subtensor.unstake` --- bittensor/core/async_subtensor.py | 6 +++--- bittensor/core/extrinsics/asyncex/unstaking.py | 6 +++--- bittensor/core/extrinsics/unstaking.py | 8 ++++---- bittensor/core/subtensor.py | 6 +++--- migration.md | 1 + tests/e2e_tests/test_staking.py | 12 ++++++------ tests/unit_tests/test_subtensor.py | 12 ++++++------ 7 files changed, 26 insertions(+), 25 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 259eff3e11..be6b322a7b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5633,7 +5633,7 @@ async def unstake( hotkey_ss58: str, amount: Balance, allow_partial_stake: bool = False, - safe_staking: bool = False, + safe_unstaking: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, raise_error: bool = False, @@ -5654,7 +5654,7 @@ async def unstake( exceed the tolerance. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. Only used when safe_staking is True. - safe_staking: If true, enables price safety checks to protect against fluctuating prices. The unstake + safe_unstaking: If true, enables price safety checks to protect against fluctuating prices. The unstake will only execute if the price change doesn't exceed the rate tolerance. period: 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. @@ -5678,7 +5678,7 @@ async def unstake( amount=amount, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, - safe_staking=safe_staking, + safe_unstaking=safe_unstaking, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 609349ec07..41723cb259 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -21,7 +21,7 @@ async def unstake_extrinsic( amount: Balance, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, - safe_staking: bool = False, + safe_unstaking: bool = False, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -37,7 +37,7 @@ async def unstake_extrinsic( hotkey_ss58: The ``ss58`` address of the hotkey to unstake from. amount: Amount to stake as Bittensor balance. allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded. - safe_staking: If true, enables price safety checks. + safe_unstaking: If true, enables price safety checks. rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). period: 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 @@ -84,7 +84,7 @@ async def unstake_extrinsic( "netuid": netuid, "amount_unstaked": amount.rao, } - if safe_staking: + if safe_unstaking: pool = await subtensor.subnet(netuid=netuid) base_price = pool.price.tao diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index fff7cdd693..5aaf162ecc 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -21,7 +21,7 @@ def unstake_extrinsic( amount: Balance, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, - safe_staking: bool = False, + safe_unstaking: bool = False, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -38,7 +38,7 @@ def unstake_extrinsic( amount: Amount to stake as Bittensor balance. allow_partial_stake: If true, allows partial unstaking if price tolerance exceeded. rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). - safe_staking: If true, enables price safety checks. + safe_unstaking: If true, enables price safety checks. period: 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. @@ -84,7 +84,7 @@ def unstake_extrinsic( "amount_unstaked": amount.rao, } - if safe_staking: + if safe_unstaking: pool = subtensor.subnet(netuid=netuid) base_price = pool.price.tao @@ -170,7 +170,7 @@ def unstake_extrinsic( ) return True - if safe_staking and "Custom error: 8" in message: + if safe_unstaking and "Custom error: 8" in message: logging.error( ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or" " enable partial staking." diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 7924b4b584..9168faa99a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4442,7 +4442,7 @@ def unstake( amount: Balance, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, - safe_staking: bool = False, + safe_unstaking: bool = False, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -4462,7 +4462,7 @@ def unstake( exceed the tolerance. rate_tolerance: The maximum allowed price change ratio when unstaking. For example, 0.005 = 0.5% maximum price decrease. Only used when safe_staking is True. - safe_staking: If true, enables price safety checks to protect against fluctuating prices. The unstake + safe_unstaking: If true, enables price safety checks to protect against fluctuating prices. The unstake will only execute if the price change doesn't exceed the rate tolerance. period: 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. @@ -4487,7 +4487,7 @@ def unstake( amount=amount, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, - safe_staking=safe_staking, + safe_unstaking=safe_unstaking, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/migration.md b/migration.md index 09004c4cf8..26646a2603 100644 --- a/migration.md +++ b/migration.md @@ -204,6 +204,7 @@ wait_for_finalization: bool = False, - parameter `netuid: Optional[int]` is now required -> `netuid: int` - parameter `hotkey_ss58: Optional[str]` is now required -> `hotkey_ss58: str` - parameter `amount: Optional[Balance]` is now required -> `amount: Balance` + - parameter `safe_staking: bool` renamed to `safe_unstaking: bool` - parameter `unstake_all: bool` removed (use `unstake_all_extrinsic` for unstake all stake) - [x] `.unstake_all_extrinsic` and `subtensor.unstake_all` - [x] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 6ea1ffa3dc..4a9e65eba5 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -800,7 +800,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=full_stake, - safe_staking=True, + safe_unstaking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) @@ -825,7 +825,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=current_stake, - safe_staking=True, + safe_unstaking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, ) @@ -847,7 +847,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=partial_unstake, - safe_staking=True, + safe_unstaking=True, rate_tolerance=0.3, # 30% allow_partial_stake=False, ) @@ -996,7 +996,7 @@ async def test_safe_staking_scenarios_async( netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=full_stake, - safe_staking=True, + safe_unstaking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) @@ -1021,7 +1021,7 @@ async def test_safe_staking_scenarios_async( netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=current_stake, - safe_staking=True, + safe_unstaking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, ) @@ -1043,7 +1043,7 @@ async def test_safe_staking_scenarios_async( netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=partial_unstake, - safe_staking=True, + safe_unstaking=True, rate_tolerance=0.3, # 30% allow_partial_stake=False, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index b0d7af970d..1044497093 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2846,7 +2846,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): amount=fake_amount, wait_for_inclusion=True, wait_for_finalization=False, - safe_staking=False, + safe_unstaking=False, allow_partial_stake=False, rate_tolerance=0.005, ) @@ -2858,7 +2858,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, amount=Balance.from_rao(fake_amount), - safe_staking=False, + safe_unstaking=False, allow_partial_stake=False, rate_tolerance=0.005, period=None, @@ -2869,8 +2869,8 @@ def test_unstake_success(mocker, subtensor, fake_wallet): assert result == mock_unstake_extrinsic.return_value -def test_unstake_with_safe_staking(mocker, subtensor, fake_wallet): - """Test unstake with safe staking parameters enabled.""" +def test_unstake_with_safe_unstaking(mocker, subtensor, fake_wallet): + """Test unstake with `safe_unstaking` parameters enabled.""" fake_hotkey_ss58 = "hotkey_1" fake_amount = 10.0 fake_netuid = 14 @@ -2886,7 +2886,7 @@ def test_unstake_with_safe_staking(mocker, subtensor, fake_wallet): amount=fake_amount, wait_for_inclusion=True, wait_for_finalization=False, - safe_staking=True, + safe_unstaking=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, ) @@ -2898,7 +2898,7 @@ def test_unstake_with_safe_staking(mocker, subtensor, fake_wallet): netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, amount=Balance.from_rao(fake_amount), - safe_staking=True, + safe_unstaking=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, period=None, From 2a925bc8da24ecc53b4ef598bd6f0e544a7cc083 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 16:43:57 -0700 Subject: [PATCH 146/177] in `.unstake_extrinsic` and `subtensor.unstake` parameter `safe_staking: bool` renamed to `safe_unstaking: bool` + in `.swap_stake_extrinsic` and `subtensor.swap_stake` parameter `safe_staking: bool` renamed to `safe_swapping: bool` --- bittensor/core/async_subtensor.py | 6 +++--- bittensor/core/extrinsics/asyncex/move_stake.py | 8 ++++---- bittensor/core/extrinsics/asyncex/unstaking.py | 2 +- bittensor/core/extrinsics/move_stake.py | 8 ++++---- bittensor/core/subtensor.py | 6 +++--- migration.md | 4 +++- tests/e2e_tests/test_staking.py | 8 ++++---- tests/unit_tests/test_subtensor.py | 8 ++++---- 8 files changed, 26 insertions(+), 24 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index be6b322a7b..17cae29646 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5432,7 +5432,7 @@ async def swap_stake( origin_netuid: int, destination_netuid: int, amount: Balance, - safe_staking: bool = False, + safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, @@ -5450,7 +5450,7 @@ async def swap_stake( origin_netuid: The netuid from which stake is removed. destination_netuid: The netuid to which stake is added. amount: The amount to swap. - safe_staking: If true, enables price safety checks to protect against fluctuating prices. The swap + safe_swapping: If true, enables price safety checks to protect against fluctuating prices. The swap will only execute if the price ratio between subnets doesn't exceed the rate tolerance. allow_partial_stake: If true and safe_staking is enabled, allows partial stake swaps when the full amount would exceed the price tolerance. If false, the entire swap fails if it would exceed the tolerance. @@ -5482,7 +5482,7 @@ async def swap_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, - safe_staking=safe_staking, + safe_swapping=safe_swapping, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 53018613c9..0934a3ce9d 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -159,7 +159,7 @@ async def swap_stake_extrinsic( origin_netuid: int, destination_netuid: int, amount: Balance, - safe_staking: bool = False, + safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, @@ -177,7 +177,7 @@ async def swap_stake_extrinsic( origin_netuid: The source subnet UID. destination_netuid: The destination subnet UID. amount: Amount to swap. - safe_staking: If true, enables price safety checks to protect against price impact. + safe_swapping: If true, enables price safety checks to protect against price impact. allow_partial_stake: If true, allows partial stake swaps when the full amount would exceed the price tolerance. rate_tolerance: Maximum allowed increase in a price ratio (0.005 = 0.5%). period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -218,7 +218,7 @@ async def swap_stake_extrinsic( "alpha_amount": amount.rao, } - if safe_staking: + if safe_swapping: origin_pool, destination_pool = await asyncio.gather( subtensor.subnet(netuid=origin_netuid), subtensor.subnet(netuid=destination_netuid), @@ -288,7 +288,7 @@ async def swap_stake_extrinsic( return True else: - if safe_staking and "Custom error: 8" in err_msg: + if safe_swapping and "Custom error: 8" in err_msg: logging.error( ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." ) diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 41723cb259..7a0e828e34 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -171,7 +171,7 @@ async def unstake_extrinsic( ) return True - if safe_staking and "Custom error: 8" in message: + if safe_unstaking and "Custom error: 8" in message: logging.error( ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." ) diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index cbef3c8117..bf7878083a 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -156,7 +156,7 @@ def swap_stake_extrinsic( origin_netuid: int, destination_netuid: int, amount: Balance, - safe_staking: bool = False, + safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, @@ -174,7 +174,7 @@ def swap_stake_extrinsic( origin_netuid: The source subnet UID. destination_netuid: The destination subnet UID. amount: Amount to swap. - safe_staking: If true, enables price safety checks to protect against price impact. + safe_swapping: If true, enables price safety checks to protect against price impact. allow_partial_stake: If true, allows partial stake swaps when the full amount would exceed the price tolerance. rate_tolerance: Maximum allowed increase in a price ratio (0.005 = 0.5%). period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -216,7 +216,7 @@ def swap_stake_extrinsic( "alpha_amount": amount.rao, } - if safe_staking: + if safe_swapping: origin_pool = subtensor.subnet(netuid=origin_netuid) destination_pool = subtensor.subnet(netuid=destination_netuid) swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao @@ -284,7 +284,7 @@ def swap_stake_extrinsic( return True else: - if safe_staking and "Custom error: 8" in err_msg: + if safe_swapping and "Custom error: 8" in err_msg: logging.error( ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 9168faa99a..faec3edb60 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4240,7 +4240,7 @@ def swap_stake( origin_netuid: int, destination_netuid: int, amount: Balance, - safe_staking: bool = False, + safe_swapping: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, period: Optional[int] = None, @@ -4258,7 +4258,7 @@ def swap_stake( origin_netuid: The netuid from which stake is removed. destination_netuid: The netuid to which stake is added. amount: The amount to swap. - safe_staking: If true, enables price safety checks to protect against fluctuating prices. The swap + safe_swapping: If true, enables price safety checks to protect against fluctuating prices. The swap will only execute if the price ratio between subnets doesn't exceed the rate tolerance. allow_partial_stake: If true and safe_staking is enabled, allows partial stake swaps when the full amount would exceed the price tolerance. If false, the entire swap fails if it would exceed the tolerance. @@ -4290,7 +4290,7 @@ def swap_stake( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, - safe_staking=safe_staking, + safe_swapping=safe_swapping, allow_partial_stake=allow_partial_stake, rate_tolerance=rate_tolerance, period=period, diff --git a/migration.md b/migration.md index 26646a2603..0b3c5a2fe5 100644 --- a/migration.md +++ b/migration.md @@ -33,7 +33,7 @@ amount: Optional[Balance] = None, rate_tolerance: float = 0.005, allow_partial_stake: bool = False, - safe_staking: bool = False, + safe_swapping: bool = False, period: Optional[int] = None, raise_error: bool = True, wait_for_inclusion: bool = True, @@ -180,6 +180,8 @@ wait_for_finalization: bool = False, - [x] `.toggle_user_liquidity_extrinsic` and `subtensor.toggle_user_liquidity` - [x] `.transfer_stake_extrinsic` and `subtensor.transfer_stake` - [x] `.swap_stake_extrinsic` and `subtensor.swap_stake` + - Changes in `swap_stake_extrinsic` and `subtensor.swap_stake`: + - parameter `safe_staking: bool` renamed to `safe_swapping: bool` - [x] `.move_stake_extrinsic` and `subtensor.move_stake` - Changes in `move_stake_extrinsic` and `subtensor.move_stake`: - parameter `origin_hotkey` renamed to `origin_hotkey_ss58` diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 4a9e65eba5..17cc94b51c 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -1120,7 +1120,7 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, - safe_staking=True, + safe_swapping=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) @@ -1146,7 +1146,7 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, - safe_staking=True, + safe_swapping=True, rate_tolerance=0.3, # 30% allow_partial_stake=True, ) @@ -1236,7 +1236,7 @@ async def test_safe_swap_stake_scenarios_async( amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, - safe_staking=True, + safe_swapping=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) @@ -1262,7 +1262,7 @@ async def test_safe_swap_stake_scenarios_async( amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, - safe_staking=True, + safe_swapping=True, rate_tolerance=0.3, # 30% allow_partial_stake=True, ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 1044497093..9f847d8bf2 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2930,7 +2930,7 @@ def test_swap_stake_success(mocker, subtensor, fake_wallet): amount=fake_amount, wait_for_inclusion=True, wait_for_finalization=False, - safe_staking=False, + safe_swapping=False, allow_partial_stake=False, rate_tolerance=0.005, ) @@ -2945,7 +2945,7 @@ def test_swap_stake_success(mocker, subtensor, fake_wallet): amount=Balance.from_rao(fake_amount), wait_for_inclusion=True, wait_for_finalization=False, - safe_staking=False, + safe_swapping=False, allow_partial_stake=False, rate_tolerance=0.005, period=None, @@ -2976,7 +2976,7 @@ def test_swap_stake_with_safe_staking(mocker, subtensor, fake_wallet): amount=fake_amount, wait_for_inclusion=True, wait_for_finalization=False, - safe_staking=True, + safe_swapping=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, ) @@ -2991,7 +2991,7 @@ def test_swap_stake_with_safe_staking(mocker, subtensor, fake_wallet): amount=Balance.from_rao(fake_amount), wait_for_inclusion=True, wait_for_finalization=False, - safe_staking=True, + safe_swapping=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, period=None, From f37c1cab6ffdc94cb75657c97493fe320e9bee1e Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 16:49:01 -0700 Subject: [PATCH 147/177] update migration.md --- migration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migration.md b/migration.md index 0b3c5a2fe5..65904a1229 100644 --- a/migration.md +++ b/migration.md @@ -53,9 +53,9 @@ 5. Since SDK is not a responsible tool, try to remove all calculations inside extrinsics that do not affect the result, but are only used in logging. Actually, this should be applied not to extrinsics only but for all codebase. -6. Remove `unstake_all` parameter from `unstake_extrinsic` since we have `unstake_all_extrinsic`which is calles another subtensor function. +6. ✅ Remove `unstake_all` parameter from `unstake_extrinsic` since we have `unstake_all_extrinsic`which is calles another subtensor function. -7. `unstake` and `unstake_multiple` extrinsics should have `safe_unstaking` parameters instead of `safe_staking`. +7. ✅ `unstake` and `unstake_multiple` extrinsics should have `safe_unstaking` parameters instead of `safe_staking`. 8. ✅ Remove `_do*` extrinsic calls and combine them with extrinsic logic. From 779717c977a67464d9d04cda4e889982e1997124 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 17:30:36 -0700 Subject: [PATCH 148/177] `test_dendrite*` no flaky --- tests/e2e_tests/test_dendrite.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index d78531ef75..f41590a33a 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -119,7 +119,6 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) - metagraph.sync() bob_neuron = metagraph.neurons[1] # Assert alpha is close to stake equivalent @@ -193,7 +192,7 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall assert await async_subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, - amount=Balance.from_tao(1), + amount=Balance.from_tao(5), wait_for_inclusion=False, wait_for_finalization=False, ) From e22bf9b2e959cbea2d7d698917ff52654fce6d68 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 18:23:35 -0700 Subject: [PATCH 149/177] test_dendrite --- tests/e2e_tests/test_dendrite.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index f41590a33a..d8d780ef6d 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -18,6 +18,8 @@ logging.on() logging.set_debug() +NON_FAST_RUNTIME_TEMPO = 10 + @pytest.mark.asyncio async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): @@ -52,13 +54,23 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): "Subnet is not active." ) - # Make sure Alice is Top Validator (for non-fast-runtime only) - if subtensor.chain.is_fast_blocks(): + if not subtensor.chain.is_fast_blocks(): + # Make sure Alice is Top Validator (for non-fast-runtime only) assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, amount=Balance.from_tao(1), ) + # set tempo to 10 block for non-fast-runtime + assert sudo_set_admin_utils( + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": alice_subnet_netuid, + "tempo": NON_FAST_RUNTIME_TEMPO, + }, + ) # update max_allowed_validators so only one neuron can get validator_permit assert sudo_set_admin_utils( @@ -187,8 +199,8 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall "Subnet is not active." ) - # Make sure Alice is Top Validator (for non-fast-runtime only) - if await async_subtensor.chain.is_fast_blocks(): + if not await async_subtensor.chain.is_fast_blocks(): + # Make sure Alice is Top Validator (for non-fast-runtime only) assert await async_subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, @@ -196,6 +208,16 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall wait_for_inclusion=False, wait_for_finalization=False, ) + # set tempo to 10 block for non-fast-runtime + 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": NON_FAST_RUNTIME_TEMPO, + }, + ) # update max_allowed_validators so only one neuron can get validator_permit assert await async_sudo_set_admin_utils( From 1c7678df495a63e71a5c65ff0ba6004bf7cca3f6 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 20:02:09 -0700 Subject: [PATCH 150/177] Changes in `.add_stake_extrinsic` and `subtensor.add_stake`: see migration.md --- bittensor/core/async_subtensor.py | 9 +- bittensor/core/extrinsics/asyncex/staking.py | 213 ++++++++----------- bittensor/core/extrinsics/staking.py | 213 ++++++++----------- bittensor/core/subtensor.py | 9 +- migration.md | 5 + tests/e2e_tests/test_delegate.py | 16 +- tests/e2e_tests/test_dendrite.py | 4 + tests/e2e_tests/test_liquid_alpha.py | 2 + tests/e2e_tests/test_liquidity.py | 16 +- tests/e2e_tests/test_metagraph.py | 6 +- tests/e2e_tests/test_root_set_weights.py | 8 +- tests/e2e_tests/test_set_weights.py | 8 +- tests/e2e_tests/test_staking.py | 74 ++----- tests/unit_tests/test_subtensor.py | 16 +- 14 files changed, 247 insertions(+), 352 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 17cae29646..f0056d124a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4277,9 +4277,9 @@ async def sign_and_send_extrinsic( async def add_stake( self, wallet: "Wallet", - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, - amount: Optional[Balance] = None, + netuid: int, + hotkey_ss58: str, + amount: Balance, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, @@ -4295,9 +4295,8 @@ async def add_stake( Parameters: wallet: The wallet to be used for staking. - hotkey_ss58: The SS58 address of the hotkey associated with the neuron to which you intend to delegate your - stake. If not specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. + hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. amount: The amount of TAO to stake. safe_staking: If true, enables price safety checks to protect against fluctuating prices. The stake will only execute if the price change doesn't exceed the rate tolerance. Default is ``False``. diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index f32d9f1a0c..c5295887e4 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -16,10 +16,9 @@ async def add_stake_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - old_balance: Optional[Balance] = None, - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, - amount: Optional[Balance] = None, + netuid: int, + hotkey_ss58: str, + amount: Balance, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, @@ -36,11 +35,9 @@ async def add_stake_extrinsic( Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object. - old_balance: the balance prior to the staking - hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. If not - specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. - amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. + hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. + amount: Amount to stake as Bittensor balance in TAO always. safe_staking: If True, enables price safety checks. Default is ``False``. allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. Default is ``False``. rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). Default is ``0.005``. @@ -63,15 +60,10 @@ async def add_stake_extrinsic( logging.error(unlock.message) return False - # Default to wallet's own hotkey if the value is not passed. - if hotkey_ss58 is None: - hotkey_ss58 = wallet.hotkey.ss58_address - logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) - if not old_balance: - old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) block_hash = await subtensor.substrate.get_chain_head() # Get current stake and existential deposit @@ -85,135 +77,116 @@ async def add_stake_extrinsic( subtensor.get_existential_deposit(block_hash=block_hash), ) - # Convert to bittensor.Balance - if amount is None: - # Stake it all. - staking_balance = Balance.from_tao(old_balance.tao) - logging.warning( - f"Didn't receive any staking amount. Staking all available balance: [blue]{staking_balance}[/blue] " - f"from wallet: [blue]{wallet.name}[/blue]" - ) - else: - staking_balance = amount - # Leave existential balance to keep key alive. - if staking_balance > old_balance - existential_deposit: + if amount > old_balance - existential_deposit: # If we are staking all, we need to leave at least the existential deposit. - staking_balance = old_balance - existential_deposit + amount = old_balance - existential_deposit else: - staking_balance = staking_balance + amount = amount # Check enough to stake. - if staking_balance > old_balance: + if amount > old_balance: logging.error(":cross_mark: [red]Not enough stake:[/red]") logging.error(f"\t\tbalance:{old_balance}") - logging.error(f"\t\tamount: {staking_balance}") + logging.error(f"\t\tamount: {amount}") logging.error(f"\t\twallet: {wallet.name}") return False - try: - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": staking_balance.rao, - } + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_staked": amount.rao, + } - if safe_staking: - pool = await subtensor.subnet(netuid=netuid) - base_price = pool.price.tao + if safe_staking: + pool = await subtensor.subnet(netuid=netuid) + base_price = pool.price.tao - if pool.netuid == 0: - price_with_tolerance = base_price - else: - price_with_tolerance = base_price * (1 + rate_tolerance) - - logging.info( - f":satellite: [magenta]Safe Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{staking_balance}[/green], " - f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " - f"price limit: [green]{price_with_tolerance}[/green], " - f"original price: [green]{base_price}[/green], " - f"with partial stake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]" - ) + price_with_tolerance = ( + base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + ) - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "add_stake_limit" - else: - logging.info( - f":satellite: [magenta]Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{staking_balance}[/green] " - f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]" - ) - call_function = "add_stake" + logging.info( + f":satellite: [magenta]Safe Staking to:[/magenta] " + f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " + f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " + f"price limit: [green]{price_with_tolerance}[/green], " + f"original price: [green]{base_price}[/green], " + f"with partial stake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]" + ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } ) - success, message = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, + call_function = "add_stake_limit" + else: + logging.info( + f":satellite: [magenta]Staking to:[/magenta] " + f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]" ) - if success: # If we successfully staked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True + call_function = "add_stake" - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, + ) + success, message = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + sign_with="coldkey", + use_nonce=True, + period=period, + raise_error=raise_error, + ) + if success: # If we successfully staked. + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return True - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] " - f"[blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - new_block_hash = await subtensor.substrate.get_chain_head() - new_balance, new_stake = await asyncio.gather( - subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=new_block_hash - ), - subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=new_block_hash, - ), - ) + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" - ) - return True + logging.info( + f":satellite: [magenta]Checking Balance on:[/magenta] " + f"[blue]{subtensor.network}[/blue] [magenta]...[/magenta]" + ) + new_block_hash = await subtensor.substrate.get_chain_head() + new_balance, new_stake = await asyncio.gather( + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=new_block_hash + ), + subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block_hash=new_block_hash, + ), + ) - if safe_staking and "Custom error: 8" in message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") - return False + logging.info( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.info( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + return True - except SubstrateRequestException as error: + if safe_staking and "Custom error: 8" in message: logging.error( - f":cross_mark: [red]Add Stake Error: {format_error_message(error)}[/red]" + ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." ) - return False + else: + logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + return False async def add_stake_multiple_extrinsic( diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index fc08c90871..629d547144 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -15,10 +15,9 @@ def add_stake_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - old_balance: Optional[Balance] = None, - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, - amount: Optional[Balance] = None, + netuid: int, + hotkey_ss58: str, + amount: Balance, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, @@ -29,16 +28,15 @@ def add_stake_extrinsic( ) -> bool: """ Adds a stake from the specified wallet to the neuron identified by the SS58 address of its hotkey in specified subnet. - Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn incentives. + Staking is a fundamental process in the Bittensor network that enables neurons to participate actively and earn + incentives. - Arguments: + Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object. - old_balance: the balance prior to the staking - hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. If not - specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. - amount: Amount to stake as Bittensor balance in TAO always, `None` if staking all. + hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. + amount: Amount to stake as Bittensor balance in TAO always. safe_staking: If True, enables price safety checks. Default is ``False``. allow_partial_stake: If True, allows partial unstaking if price tolerance exceeded. Default is ``False``. rate_tolerance: Maximum allowed price increase percentage (0.005 = 0.5%). Default is ``0.005``. @@ -61,15 +59,10 @@ def add_stake_extrinsic( logging.error(unlock.message) return False - # Default to wallet's own hotkey if the value is not passed. - if hotkey_ss58 is None: - hotkey_ss58 = wallet.hotkey.ss58_address - logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" ) - if not old_balance: - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) block = subtensor.get_current_block() # Get current stake and existential deposit @@ -81,131 +74,113 @@ def add_stake_extrinsic( ) existential_deposit = subtensor.get_existential_deposit(block=block) - # Convert to bittensor.Balance - if amount is None: - # Stake it all. - staking_balance = Balance.from_tao(old_balance.tao) - logging.warning( - f"Didn't receive any staking amount. Staking all available balance: [blue]{staking_balance}[/blue] " - f"from wallet: [blue]{wallet.name}[/blue]" - ) - else: - staking_balance = amount - # Leave existential balance to keep key alive. - if staking_balance > old_balance - existential_deposit: + if amount > old_balance - existential_deposit: # If we are staking all, we need to leave at least the existential deposit. - staking_balance = old_balance - existential_deposit + amount = old_balance - existential_deposit # Check enough to stake. - if staking_balance > old_balance: + if amount > old_balance: logging.error(":cross_mark: [red]Not enough stake:[/red]") logging.error(f"\t\tbalance:{old_balance}") - logging.error(f"\t\tamount: {staking_balance}") + logging.error(f"\t\tamount: {amount}") logging.error(f"\t\twallet: {wallet.name}") return False - try: - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": staking_balance.rao, - } + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_staked": amount.rao, + } - if safe_staking: - pool = subtensor.subnet(netuid=netuid) - base_price = pool.price.tao + if safe_staking: + pool = subtensor.subnet(netuid=netuid) + base_price = pool.price.tao - if pool.netuid == 0: - price_with_tolerance = base_price - else: - price_with_tolerance = base_price * (1 + rate_tolerance) - - logging.info( - f":satellite: [magenta]Safe Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{staking_balance}[/green], " - f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " - f"price limit: [green]{price_with_tolerance}[/green], " - f"original price: [green]{base_price}[/green], " - f"with partial stake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]" - ) - - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "add_stake_limit" - else: - logging.info( - f":satellite: [magenta]Staking to:[/magenta] " - f"[blue]netuid: [green]{netuid}[/green], amount: [green]{staking_balance}[/green] " - f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]" - ) - call_function = "add_stake" + price_with_tolerance = ( + base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + logging.info( + f":satellite: [magenta]Safe Staking to:[/magenta] " + f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green], " + f"tolerance percentage: [green]{rate_tolerance * 100}%[/green], " + f"price limit: [green]{price_with_tolerance}[/green], " + f"original price: [green]{base_price}[/green], " + f"with partial stake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue][/magenta]...[/magenta]" ) - success, message = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - sign_with="coldkey", - nonce_key="coldkeypub", - period=period, - raise_error=raise_error, + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } + ) + call_function = "add_stake_limit" + else: + logging.info( + f":satellite: [magenta]Staking to:[/magenta] " + f"[blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue][magenta]...[/magenta]" ) - if success is True: # If we successfully staked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return True + call_function = "add_stake" - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, + ) - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] " - f"[blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - new_block = subtensor.get_current_block() - new_balance = subtensor.get_balance( - wallet.coldkeypub.ss58_address, block=new_block - ) - new_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=new_block, - ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: {new_balance}[/green]" - ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" - ) + success, message = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + sign_with="coldkey", + nonce_key="coldkeypub", + period=period, + raise_error=raise_error, + ) + # If we successfully staked. + if success: + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: return True - if safe_staking and "Custom error: 8" in message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") - return False + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - except SubstrateRequestException as error: + logging.info( + f":satellite: [magenta]Checking Balance on:[/magenta] " + f"[blue]{subtensor.network}[/blue] [magenta]...[/magenta]" + ) + new_block = subtensor.get_current_block() + new_balance = subtensor.get_balance( + wallet.coldkeypub.ss58_address, block=new_block + ) + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block=new_block, + ) + logging.info( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: {new_balance}[/green]" + ) + logging.info( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + return True + + if safe_staking and "Custom error: 8" in message: logging.error( - f":cross_mark: [red]Add Stake Error: {format_error_message(error)}[/red]" + ":cross_mark: [red]Failed[/red]: Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." ) - return False + else: + logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + return False def add_stake_multiple_extrinsic( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index faec3edb60..ff8c1c27d5 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3117,9 +3117,9 @@ def sign_and_send_extrinsic( def add_stake( self, wallet: "Wallet", - hotkey_ss58: Optional[str] = None, - netuid: Optional[int] = None, - amount: Optional[Balance] = None, + netuid: int, + hotkey_ss58: str, + amount: Balance, safe_staking: bool = False, allow_partial_stake: bool = False, rate_tolerance: float = 0.005, @@ -3135,9 +3135,8 @@ def add_stake( Parameters: wallet: The wallet to be used for staking. - hotkey_ss58: The SS58 address of the hotkey associated with the neuron to which you intend to delegate your - stake. If not specified, the wallet's hotkey will be used. netuid: The unique identifier of the subnet to which the neuron belongs. + hotkey_ss58: The `ss58` address of the hotkey account to stake to default to the wallet's hotkey. amount: The amount of TAO to stake. safe_staking: If true, enables price safety checks to protect against fluctuating prices. The stake will only execute if the price change doesn't exceed the rate tolerance. Default is ``False``. diff --git a/migration.md b/migration.md index 65904a1229..3d9a3d9f1f 100644 --- a/migration.md +++ b/migration.md @@ -197,6 +197,11 @@ wait_for_finalization: bool = False, - [x] `subtensor.comit` renamed to `subtensor.set_commitment` - [x] `.publish_metadata`, `subtensor.set_commitment` and `subtenor.set_reveal_commitment` - [x] `.add_stake_extrinsic` and `subtensor.add_stake` + - Changes in `.add_stake_extrinsic` and `subtensor.add_stake`: + - parameter `old_balance` removed from async version + - parameter `netuid` required (no Optional anymore) + - parameter `hotkey_ss58` required (no Optional anymore) + - parameter `amount` required (no Optional anymore) - [x] `.add_stake_multiple_extrinsic` and `subtensor.add_stake_multiple` - [x] `.start_call_extrinsic` and `subtensor.start_call` - [x] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 1a36389b57..04417f6615 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -452,11 +452,9 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): subtensor.staking.add_stake( wallet=bob_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - wait_for_inclusion=True, - wait_for_finalization=True, ) # let chain update validator_permits @@ -619,11 +617,9 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): await async_subtensor.staking.add_stake( wallet=bob_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - wait_for_inclusion=True, - wait_for_finalization=True, ) # let chain update validator_permits @@ -686,11 +682,9 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ success = subtensor.staking.add_stake( wallet=dave_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), - wait_for_inclusion=True, - wait_for_finalization=True, ) assert success is True @@ -771,11 +765,9 @@ async def test_nominator_min_required_stake_async( success = await async_subtensor.staking.add_stake( wallet=dave_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), - wait_for_inclusion=True, - wait_for_finalization=True, ) assert success is True diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index d8d780ef6d..73d646aadf 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -59,6 +59,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), ) # set tempo to 10 block for non-fast-runtime @@ -126,6 +127,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): assert subtensor.staking.add_stake( wallet=bob_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, ) @@ -204,6 +206,7 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall assert await async_subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(5), wait_for_inclusion=False, wait_for_finalization=False, @@ -273,6 +276,7 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall assert await async_subtensor.staking.add_stake( wallet=bob_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, wait_for_inclusion=False, wait_for_finalization=False, diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 3306deb072..e31a852933 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -60,6 +60,7 @@ def test_liquid_alpha(subtensor, alice_wallet): assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ), "Unable to stake to Alice neuron" @@ -241,6 +242,7 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): assert await async_subtensor.staking.add_stake( wallet=alice_wallet, netuid=netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ), "Unable to stake to Alice neuron" diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index c99b23338d..2617a6259c 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -76,13 +76,11 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): assert message == "", "❌ Cannot enable user liquidity." # Add steak to call add_liquidity - assert subtensor.extrinsics.add_stake( + assert subtensor.staking.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 @@ -193,13 +191,11 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): ) # Add stake from Bob to Alice - assert subtensor.extrinsics.add_stake( + assert subtensor.staking.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 @@ -371,13 +367,11 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert message == "", "❌ Cannot enable user liquidity." # Add steak to call add_liquidity - assert await async_subtensor.extrinsics.add_stake( + assert await async_subtensor.staking.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 @@ -490,13 +484,11 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): ) # Add stake from Bob to Alice - assert await async_subtensor.extrinsics.add_stake( + assert await async_subtensor.staking.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 diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 570eefc460..d59b944162 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -143,9 +143,8 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): assert subtensor.staking.add_stake( wallet=bob_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, 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") @@ -313,9 +312,8 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w assert await async_subtensor.staking.add_stake( wallet=bob_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, 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") diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index 7ce08d9f47..7d18dccfcf 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -88,11 +88,9 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall assert subtensor.staking.add_stake( wallet=bob_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), - wait_for_inclusion=True, - wait_for_finalization=True, period=16, ), "Unable to stake from Bob to Alice" @@ -213,11 +211,9 @@ async def test_root_reg_hyperparams_async( assert await async_subtensor.staking.add_stake( wallet=bob_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), - wait_for_inclusion=True, - wait_for_finalization=True, period=16, ), "Unable to stake from Bob to Alice" diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 4024a34e5d..ea754b68c8 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -83,11 +83,9 @@ def test_set_weights_uses_next_nonce(subtensor, alice_wallet): for netuid in netuids: assert subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - wait_for_inclusion=True, - wait_for_finalization=True, ) # Set weight hyperparameters per subnet @@ -249,11 +247,9 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): for netuid in netuids: assert await async_subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - wait_for_inclusion=True, - wait_for_finalization=True, ) # Set weight hyperparameters per subnet diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 17cc94b51c..b940999825 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -59,11 +59,9 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): success = subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), - wait_for_inclusion=True, - wait_for_finalization=True, period=16, ) @@ -239,11 +237,9 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) success = await async_subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), - wait_for_inclusion=True, - wait_for_finalization=True, period=16, ) @@ -725,11 +721,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 1. Strict params - should fail success = subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, @@ -749,11 +743,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 2. Partial allowed - should succeed partially success = subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, @@ -776,11 +768,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) amount = Balance.from_tao(100) success = subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=amount, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.22, # 22% allow_partial_stake=False, @@ -921,11 +911,9 @@ async def test_safe_staking_scenarios_async( # 1. Strict params - should fail success = await async_subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=False, @@ -945,11 +933,9 @@ async def test_safe_staking_scenarios_async( # 2. Partial allowed - should succeed partially success = await async_subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, @@ -972,11 +958,9 @@ async def test_safe_staking_scenarios_async( 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, + hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=amount, - wait_for_inclusion=True, - wait_for_finalization=True, safe_staking=True, rate_tolerance=0.22, # 22% allow_partial_stake=False, @@ -1093,11 +1077,9 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): 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, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=initial_stake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert success is True @@ -1209,11 +1191,9 @@ async def test_safe_swap_stake_scenarios_async( 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, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=initial_stake_amount, - wait_for_inclusion=True, - wait_for_finalization=True, ) assert success is True @@ -1301,11 +1281,9 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, 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) @@ -1399,11 +1377,9 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert subtensor.staking.add_stake( wallet=dave_wallet, - hotkey_ss58=dave_wallet.hotkey.ss58_address, netuid=bob_subnet_netuid, + hotkey_ss58=dave_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), - wait_for_inclusion=True, - wait_for_finalization=True, allow_partial_stake=True, ) @@ -1464,11 +1440,9 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ assert await async_subtensor.staking.add_stake( wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), - wait_for_inclusion=True, - wait_for_finalization=True, ) stakes = await async_subtensor.staking.get_stake_for_coldkey( @@ -1571,8 +1545,6 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ 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, ) @@ -1635,12 +1607,10 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ) assert subtensor.staking.add_stake( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), - wait_for_inclusion=True, - wait_for_finalization=True, ) alice_stakes = subtensor.staking.get_stake_for_coldkey( @@ -1766,12 +1736,10 @@ async def test_transfer_stake_async( ) assert await async_subtensor.staking.add_stake( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), - wait_for_inclusion=True, - wait_for_finalization=True, ) alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( @@ -1941,8 +1909,6 @@ def test_unstaking_with_limit( 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 subtensor.staking.add_stake( @@ -1950,8 +1916,6 @@ def test_unstaking_with_limit( 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}" @@ -2061,8 +2025,6 @@ async def test_unstaking_with_limit_async( netuid=alice_subnet_netuid_2, hotkey_ss58=dave_wallet.hotkey.ss58_address, 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( @@ -2070,8 +2032,6 @@ async def test_unstaking_with_limit_async( netuid=alice_subnet_netuid_3, hotkey_ss58=alice_wallet.hotkey.ss58_address, 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}" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 9f847d8bf2..a8882e9897 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2717,7 +2717,8 @@ def test_add_stake_success(mocker, fake_wallet, subtensor): """Test add_stake returns True on successful staking.""" # Prep fake_hotkey_ss58 = "fake_hotkey" - fake_amount = 10.0 + fake_amount = Balance.from_tao(10.0) + fake_netuid = 14 mock_add_stake_extrinsic = mocker.patch.object( subtensor_module, "add_stake_extrinsic" @@ -2726,6 +2727,7 @@ def test_add_stake_success(mocker, fake_wallet, subtensor): # Call result = subtensor.add_stake( wallet=fake_wallet, + netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, amount=fake_amount, wait_for_inclusion=True, @@ -2740,8 +2742,8 @@ def test_add_stake_success(mocker, fake_wallet, subtensor): subtensor=subtensor, wallet=fake_wallet, hotkey_ss58=fake_hotkey_ss58, - netuid=None, - amount=Balance.from_rao(fake_amount), + netuid=14, + amount=fake_amount.rao, wait_for_inclusion=True, wait_for_finalization=False, safe_staking=False, @@ -2756,8 +2758,9 @@ def test_add_stake_success(mocker, fake_wallet, subtensor): def test_add_stake_with_safe_staking(mocker, fake_wallet, subtensor): """Test add_stake with safe staking parameters enabled.""" # Prep + fake_netuid = 14 fake_hotkey_ss58 = "fake_hotkey" - fake_amount = 10.0 + fake_amount = Balance.from_tao(10.0) fake_rate_tolerance = 0.01 # 1% threshold mock_add_stake_extrinsic = mocker.patch.object( @@ -2767,6 +2770,7 @@ def test_add_stake_with_safe_staking(mocker, fake_wallet, subtensor): # Call result = subtensor.add_stake( wallet=fake_wallet, + netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, amount=fake_amount, wait_for_inclusion=True, @@ -2781,8 +2785,8 @@ def test_add_stake_with_safe_staking(mocker, fake_wallet, subtensor): subtensor=subtensor, wallet=fake_wallet, hotkey_ss58=fake_hotkey_ss58, - netuid=None, - amount=Balance.from_rao(fake_amount), + netuid=14, + amount=fake_amount.rao, wait_for_inclusion=True, wait_for_finalization=False, safe_staking=True, From e00cbdb9f86184c26ec88553a1671e35172bd5d9 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 21:26:13 -0700 Subject: [PATCH 151/177] Changes in `.add_stake_multiple_extrinsic` and `subtensor.add_stake_multiple`: see migration.md --- bittensor/core/async_subtensor.py | 10 +- bittensor/core/extrinsics/asyncex/staking.py | 98 ++++++++------------ bittensor/core/extrinsics/staking.py | 96 ++++++++----------- bittensor/core/subtensor.py | 10 +- migration.md | 3 + tests/e2e_tests/test_staking.py | 6 +- 6 files changed, 93 insertions(+), 130 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index f0056d124a..14f91c1854 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4392,7 +4392,7 @@ async def add_stake_multiple( wallet: "Wallet", hotkey_ss58s: list[str], netuids: list[int], - amounts: Optional[list[Balance]] = None, + amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -4402,11 +4402,11 @@ async def add_stake_multiple( Adds stakes to multiple neurons identified by their hotkey SS58 addresses. This bulk operation allows for efficient staking across different neurons from a single wallet. - Arguments: + Parameters: wallet: The wallet used for staking. hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. - netuids: list of subnet UIDs. - amounts: Corresponding amounts of TAO to stake for each hotkey. + netuids: List of subnet UIDs. + amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. period: 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. @@ -4423,8 +4423,8 @@ async def add_stake_multiple( return await add_stake_multiple_extrinsic( subtensor=self, wallet=wallet, - hotkey_ss58s=hotkey_ss58s, netuids=netuids, + hotkey_ss58s=hotkey_ss58s, amounts=amounts, period=period, raise_error=raise_error, diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index c5295887e4..c06c58cd78 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -194,23 +194,22 @@ async def add_stake_multiple_extrinsic( wallet: "Wallet", hotkey_ss58s: list[str], netuids: list[int], - old_balance: Optional[Balance] = None, - amounts: Optional[list[Balance]] = None, + amounts: list[Balance], period: Optional[int] = None, - raise_error: bool = True, + raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ - Adds a stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. + Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey on subnet with + corresponding netuid. Parameters: - subtensor: AsyncSubtensor instance with the connection to the chain. + subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object for the coldkey. - old_balance: The balance of the wallet prior to staking. - hotkey_ss58s: List of hotkeys to stake to. netuids: List of netuids to stake to. - amounts: List of amounts to stake. If `None`, stake all to the first hotkey. + hotkey_ss58s: List of hotkeys to stake to. + amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. period: 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. @@ -221,35 +220,36 @@ async def add_stake_multiple_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ - if not isinstance(hotkey_ss58s, list) or not all( - isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s - ): - raise TypeError("hotkey_ss58s must be a list of str") + # Decrypt keys, + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return False + + assert all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." if len(hotkey_ss58s) == 0: return True - if amounts is not None and len(amounts) != len(hotkey_ss58s): - raise ValueError("amounts must be a list of the same length as hotkey_ss58s") + assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." + ) - if len(netuids) != len(hotkey_ss58s): - raise ValueError("netuids must be a list of the same length as hotkey_ss58s") + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("hotkey_ss58s must be a list of str") - new_amounts: Sequence[Optional[Balance]] - if amounts is None: - new_amounts = [None] * len(hotkey_ss58s) - else: - new_amounts = [ - amount.set_unit(netuid=netuid) for amount, netuid in zip(amounts, netuids) - ] - if sum(amount.tao for amount in new_amounts) == 0: - # Staking 0 tao - return True + new_amounts: Sequence[Optional[Balance]] = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + if sum(amount.tao for amount in new_amounts) == 0: + # Staking 0 tao + return True logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -268,11 +268,9 @@ async def add_stake_multiple_extrinsic( total_staking_rao = sum( [amount.rao if amount is not None else 0 for amount in new_amounts] ) - if old_balance is None: - old_balance = await subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=block_hash - ) - initial_balance = old_balance + old_balance = initial_balance = await subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ) if total_staking_rao == 0: # Staking all to the first wallet. @@ -294,28 +292,17 @@ async def add_stake_multiple_extrinsic( for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( zip(hotkey_ss58s, new_amounts, old_stakes, netuids) ): - staking_all = False - # Convert to bittensor.Balance - if amount is None: - # Stake it all. - staking_balance = Balance.from_tao(old_balance.tao) - staking_all = True - else: - # Amounts are cast to balance earlier in the function - assert isinstance(amount, Balance) - staking_balance = amount - # Check enough to stake - if staking_balance > old_balance: + if amount > old_balance: logging.error( f":cross_mark: [red]Not enough balance[/red]: [green]{old_balance}[/green] to stake: " - f"[blue]{staking_balance}[/blue] from wallet: [white]{wallet.name}[/white]" + f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white]" ) continue try: logging.info( - f"Staking [blue]{staking_balance}[/blue] to hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " + f"Staking [blue]{amount}[/blue] to hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " f"[blue]{netuid}[/blue]" ) call = await subtensor.substrate.compose_call( @@ -323,7 +310,7 @@ async def add_stake_multiple_extrinsic( call_function="add_stake", call_params={ "hotkey": hotkey_ss58, - "amount_staked": staking_balance.rao, + "amount_staked": amount.rao, "netuid": netuid, }, ) @@ -342,12 +329,8 @@ async def add_stake_multiple_extrinsic( # If we successfully staked. if success: if not wait_for_finalization and not wait_for_inclusion: - old_balance -= staking_balance + old_balance -= amount successful_stakes += 1 - if staking_all: - # If staked all, no need to continue - break - continue logging.success(":white_heavy_check_mark: [green]Finalized[/green]") @@ -373,10 +356,6 @@ async def add_stake_multiple_extrinsic( ) old_balance = new_balance successful_stakes += 1 - if staking_all: - # If staked all, no need to continue - break - else: logging.error(f":cross_mark: [red]Failed: {message}.[/red]") continue @@ -385,7 +364,6 @@ async def add_stake_multiple_extrinsic( logging.error( f":cross_mark: [red]Add Stake Multiple error: {format_error_message(error)}[/red]" ) - continue if successful_stakes != 0: logging.info( diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 629d547144..eefe5ea65d 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -188,23 +188,22 @@ def add_stake_multiple_extrinsic( wallet: "Wallet", hotkey_ss58s: list[str], netuids: list[int], - old_balance: Optional[Balance] = None, - amounts: Optional[list[Balance]] = None, + amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> bool: """ - Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. + Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey on subnet with + corresponding netuid. Parameters: subtensor: Subtensor instance with the connection to the chain. wallet: Bittensor wallet object for the coldkey. - old_balance: The balance of the wallet prior to staking. - hotkey_ss58s: List of hotkeys to stake to. netuids: List of netuids to stake to. - amounts: List of amounts to stake. If `None`, stake all to the first hotkey. + hotkey_ss58s: List of hotkeys to stake to. + amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. period: 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. @@ -215,36 +214,36 @@ def add_stake_multiple_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ - if not isinstance(hotkey_ss58s, list) or not all( - isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s - ): - raise TypeError("hotkey_ss58s must be a list of str") + # Decrypt keys, + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return False + + assert all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." if len(hotkey_ss58s) == 0: return True - if amounts is not None and len(amounts) != len(hotkey_ss58s): - raise ValueError("amounts must be a list of the same length as hotkey_ss58s") + assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." + ) - if netuids is not None and len(netuids) != len(hotkey_ss58s): - raise ValueError("netuids must be a list of the same length as hotkey_ss58s") + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("hotkey_ss58s must be a list of str") - new_amounts: Sequence[Optional[Balance]] + new_amounts: Sequence[Optional[Balance]] = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] - if amounts is None: - new_amounts = [None] * len(hotkey_ss58s) - else: - new_amounts = [ - amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) - ] - if sum(amount.tao for amount in new_amounts) == 0: - # Staking 0 tao - return True - - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + if sum(amount.tao for amount in new_amounts) == 0: + # Staking 0 tao + return True logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -262,10 +261,9 @@ def add_stake_multiple_extrinsic( total_staking_rao = sum( [amount.rao if amount is not None else 0 for amount in new_amounts] ) - if old_balance is None: - old_balance = initial_balance = subtensor.get_balance( - wallet.coldkeypub.ss58_address, block=block - ) + old_balance = initial_balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block=block + ) if total_staking_rao == 0: # Staking all to the first wallet. if old_balance.rao > 1000: @@ -286,32 +284,25 @@ def add_stake_multiple_extrinsic( for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( zip(hotkey_ss58s, new_amounts, old_stakes, netuids) ): - staking_all = False - if amount is None: - # Stake it all. - staking_balance = Balance.from_tao(old_balance.tao) - staking_all = True - else: - staking_balance = amount - # Check enough to stake - if staking_balance > old_balance: + if amount > old_balance: logging.error( f":cross_mark: [red]Not enough balance[/red]: [green]{old_balance}[/green] to stake: " - f"[blue]{staking_balance}[/blue] from wallet: [white]{wallet.name}[/white]" + f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white]" ) continue try: logging.info( - f"Staking [blue]{staking_balance}[/blue] to [magenta]{hotkey_ss58}[/magenta] on netuid [blue]{netuid}[/blue]" + f"Staking [blue]{amount}[/blue] to hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " + f"[blue]{netuid}[/blue]" ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="add_stake", call_params={ "hotkey": hotkey_ss58, - "amount_staked": staking_balance.rao, + "amount_staked": amount.rao, "netuid": netuid, }, ) @@ -327,16 +318,12 @@ def add_stake_multiple_extrinsic( raise_error=raise_error, ) - if success is True: # If we successfully staked. + # If we successfully staked. + if success: # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - old_balance -= staking_balance + old_balance -= amount successful_stakes += 1 - if staking_all: - # If staked all, no need to continue - break - continue logging.success(":white_heavy_check_mark: [green]Finalized[/green]") @@ -359,10 +346,6 @@ def add_stake_multiple_extrinsic( ) old_balance = new_balance successful_stakes += 1 - if staking_all: - # If staked all, no need to continue - break - else: logging.error(f":cross_mark: [red]Failed[/red]: {message}") continue @@ -371,7 +354,6 @@ def add_stake_multiple_extrinsic( logging.error( f":cross_mark: [red]Add Stake Multiple error: {format_error_message(error)}[/red]" ) - continue if successful_stakes != 0: logging.info( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ff8c1c27d5..435498038c 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3232,7 +3232,7 @@ def add_stake_multiple( wallet: "Wallet", hotkey_ss58s: list[str], netuids: list[int], - amounts: Optional[list[Balance]] = None, + amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -3242,11 +3242,11 @@ def add_stake_multiple( Adds stakes to multiple neurons identified by their hotkey SS58 addresses. This bulk operation allows for efficient staking across different neurons from a single wallet. - Arguments: + Parameters: wallet: The wallet used for staking. hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. - netuids: list of subnet UIDs. - amounts: Corresponding amounts of TAO to stake for each hotkey. + netuids: List of subnet UIDs. + amounts: List of corresponding TAO amounts to bet for each netuid and hotkey. period: 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. @@ -3263,8 +3263,8 @@ def add_stake_multiple( return add_stake_multiple_extrinsic( subtensor=self, wallet=wallet, - hotkey_ss58s=hotkey_ss58s, netuids=netuids, + hotkey_ss58s=hotkey_ss58s, amounts=amounts, period=period, raise_error=raise_error, diff --git a/migration.md b/migration.md index 3d9a3d9f1f..4ff532c5a7 100644 --- a/migration.md +++ b/migration.md @@ -203,6 +203,9 @@ wait_for_finalization: bool = False, - parameter `hotkey_ss58` required (no Optional anymore) - parameter `amount` required (no Optional anymore) - [x] `.add_stake_multiple_extrinsic` and `subtensor.add_stake_multiple` + - Changes in `.add_stake_multiple_extrinsic` and `subtensor.add_stake_multiple`: + - parameter `old_balance` removed from async version + - parameter `amounts` required (no Optional anymore) - [x] `.start_call_extrinsic` and `subtensor.start_call` - [x] `.increase_take_extrinsic`, `.decrease_take_extrinsic` and `subtenor.set_reveal_commitment` - [x] `.transfer_extrinsic` and `subtensor.transfer` diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index b940999825..b3847bb507 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -428,7 +428,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): alice_balance = balances[alice_wallet.coldkey.ss58_address] success = subtensor.staking.add_stake_multiple( - alice_wallet, + wallet=alice_wallet, hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], netuids=netuids, amounts=[Balance.from_tao(10_000) for _ in netuids], @@ -438,8 +438,8 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): stakes = [ subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=netuid, ) for netuid in netuids From 37f49e6dbb65d30603f66f8740153da91191bf6a Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 21:58:45 -0700 Subject: [PATCH 152/177] limit `max-parallel` to 16 * 4 (py versions in reusable workflow) --- .github/workflows/e2e-subtensor-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index dd819957f4..261c8bda3c 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -138,6 +138,7 @@ jobs: - pull-docker-image strategy: fail-fast: false + max-parallel: 16 matrix: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} uses: ./.github/workflows/_run-e2e-single.yaml From 2afa67d2c60bb940660d7d30f23eb509e5fc2e86 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 23:13:28 -0700 Subject: [PATCH 153/177] `amount: Optional[Balance] = None` in `move_stake_extrinsic` bbc of logic required --- bittensor/core/extrinsics/asyncex/move_stake.py | 2 +- bittensor/core/extrinsics/move_stake.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 0934a3ce9d..a401350251 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -308,7 +308,7 @@ async def move_stake_extrinsic( origin_netuid: int, destination_hotkey_ss58: str, destination_netuid: int, - amount: Balance, + amount: Optional[Balance] = None, move_all_stake: bool = False, period: Optional[int] = None, raise_error: bool = False, diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index bf7878083a..b3a9d54e95 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -304,7 +304,7 @@ def move_stake_extrinsic( origin_netuid: int, destination_hotkey_ss58: str, destination_netuid: int, - amount: Balance, + amount: Optional[Balance] = None, move_all_stake: bool = False, period: Optional[int] = None, raise_error: bool = False, From 70db4498c51e7f0ab849e212f2aee9325d7bb936 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 23:14:32 -0700 Subject: [PATCH 154/177] improved `unstake_multiple_extrinsic` logic --- .../core/extrinsics/asyncex/unstaking.py | 53 +++++++++++-------- bittensor/core/extrinsics/unstaking.py | 53 +++++++++++-------- migration.md | 2 + 3 files changed, 65 insertions(+), 43 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 7a0e828e34..bda16a0835 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -286,29 +286,19 @@ async def unstake_multiple_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ + # Unlock coldkey. + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return False + + # or amounts or unstake_all (no both) if amounts and unstake_all: raise ValueError("Cannot specify both `amounts` and `unstake_all`.") - if not isinstance(hotkey_ss58s, list) or not all( - isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s - ): - raise TypeError("hotkey_ss58s must be a list of str") - - if len(hotkey_ss58s) == 0: - return True - - if amounts is not None and len(amounts) != len(hotkey_ss58s): - raise ValueError("amounts must be a list of the same length as hotkey_ss58s") - - if netuids is not None and len(netuids) != len(hotkey_ss58s): - raise ValueError("netuids must be a list of the same length as hotkey_ss58s") - if amounts is not None and not all( - isinstance(amount, (Balance, float)) for amount in amounts + isinstance(amount, Balance) for amount in amounts ): - raise TypeError( - "amounts must be a [list of bittensor.Balance or float] or None" - ) + raise TypeError("amounts must be a [list of bittensor.Balance] or None") if amounts is None: amounts = [None] * len(hotkey_ss58s) @@ -319,10 +309,29 @@ async def unstake_multiple_extrinsic( # Staking 0 tao return True - # Unlock coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + assert all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + + if len(hotkey_ss58s) == 0: + return True + + assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." + ) + + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("hotkey_ss58s must be a list of str") + + if amounts is not None and len(amounts) != len(hotkey_ss58s): + raise ValueError("amounts must be a list of the same length as hotkey_ss58s") + + if netuids is not None and len(netuids) != len(hotkey_ss58s): + raise ValueError("netuids must be a list of the same length as hotkey_ss58s") logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 5aaf162ecc..d9539a8268 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -287,23 +287,15 @@ def unstake_multiple_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ + # Unlock coldkey. + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return False + + # or amounts or unstake_all (no both) if amounts and unstake_all: raise ValueError("Cannot specify both `amounts` and `unstake_all`.") - if not isinstance(hotkey_ss58s, list) or not all( - isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s - ): - raise TypeError("hotkey_ss58s must be a list of str") - - if len(hotkey_ss58s) == 0: - return True - - if netuids is not None and len(netuids) != len(hotkey_ss58s): - raise ValueError("netuids must be a list of the same length as hotkey_ss58s") - - if amounts is not None and len(amounts) != len(hotkey_ss58s): - raise ValueError("amounts must be a list of the same length as hotkey_ss58s") - if amounts is not None and not all( isinstance(amount, Balance) for amount in amounts ): @@ -318,10 +310,29 @@ def unstake_multiple_extrinsic( # Staking 0 tao return True - # Unlock coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + assert all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + + if len(hotkey_ss58s) == 0: + return True + + assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." + ) + + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("hotkey_ss58s must be a list of str") + + if amounts is not None and len(amounts) != len(hotkey_ss58s): + raise ValueError("amounts must be a list of the same length as hotkey_ss58s") + + if netuids is not None and len(netuids) != len(hotkey_ss58s): + raise ValueError("netuids must be a list of the same length as hotkey_ss58s") logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -377,7 +388,7 @@ def unstake_multiple_extrinsic( f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]" ) - staking_response, err_msg = subtensor.sign_and_send_extrinsic( + success, message = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -389,7 +400,7 @@ def unstake_multiple_extrinsic( raise_error=raise_error, ) - if staking_response: # If we successfully unstaked. + if success: # If we successfully unstaked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: @@ -413,7 +424,7 @@ def unstake_multiple_extrinsic( ) successful_unstakes += 1 else: - logging.error(f":cross_mark: [red]Failed: {err_msg}.[/red]") + logging.error(f":cross_mark: [red]Failed: {message}.[/red]") continue except SubstrateRequestException as error: diff --git a/migration.md b/migration.md index 4ff532c5a7..cc06b7b66f 100644 --- a/migration.md +++ b/migration.md @@ -218,6 +218,8 @@ wait_for_finalization: bool = False, - parameter `unstake_all: bool` removed (use `unstake_all_extrinsic` for unstake all stake) - [x] `.unstake_all_extrinsic` and `subtensor.unstake_all` - [x] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` + - Changes in `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple`: + - parameter `amounts` is now required (no Optional anymore) - [x] `.commit_weights_extrinsic` and `subtensor.commit_weights` - [x] `.reveal_weights_extrinsic` and `subtensor.reveal_weights` - [x] `.set_weights_extrinsic` and `subtensor.set_weights` \ No newline at end of file From d01b2a9d3fe66568d34edfa788c9d81ad6ab28c3 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 23:21:41 -0700 Subject: [PATCH 155/177] updated migration.md --- migration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/migration.md b/migration.md index cc06b7b66f..3e4b0a7a1a 100644 --- a/migration.md +++ b/migration.md @@ -108,6 +108,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. 13. ✅ The SDK is dropping support for `Python 3.9` starting with this release. 14. Remove `Default is` and `Default to` in docstrings bc parameters enough. +15. camfairchild: TODO, but we should have a grab_metadata if we don't already. Maybe don't decode, but can have a call that removes the Raw prefix, and another just doing grab_metadata_raw (no decoding) ## 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`) From 169d7a0faf789f987a497bc6ddf108068e0d6f8f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 00:55:54 -0700 Subject: [PATCH 156/177] improve `find-tests` --- .github/workflows/e2e-subtensor-tests.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 261c8bda3c..7160006a3e 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -43,6 +43,12 @@ jobs: python -m pip install --upgrade pip pip install -e ".[dev]" + - name: Install uv + uses: astral-sh/setup-uv@v4 + + - name: Install dependencies + run: uv sync --extra dev --dev + - name: Find test files id: get-tests shell: bash From 6b2614f9c1407e16ed2161036a044049d298533e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:23:40 -0700 Subject: [PATCH 157/177] improve workflow for e2e tests --- .github/workflows/_run-e2e-single.yaml | 17 ++++++- .github/workflows/e2e-subtensor-tests.yaml | 56 ++++++++++++++++++---- 2 files changed, 63 insertions(+), 10 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 16c41269af..76768340b2 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -9,6 +9,9 @@ on: image-name: required: true type: string + python-versions: + required: true + type: string jobs: run-e2e: @@ -19,7 +22,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(inputs.python-versions) }} steps: - name: Check-out repository @@ -32,6 +35,18 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Cache uv and venv + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + .venv + key: uv-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + restore-keys: | + uv-${{ runner.os }}-py${{ matrix.python-version }}- - name: Install dependencies run: uv sync --extra dev --dev diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 7160006a3e..991fb51c32 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -20,11 +20,46 @@ on: env: CARGO_TERM_COLOR: always VERBOSE: ${{ github.event.inputs.verbose }} + PY_VERSION: '["3.11","3.12","3.13"]' # job to run tests in parallel jobs: + + prepare-deps: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ${{ fromJson(env.PY_VERSIONS_JSON) }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + + - name: Cache uv and venv + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + .venv + key: uv-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + restore-keys: | + uv-${{ runner.os }}-py${{ matrix.python-version }}- + + - name: Sync deps (populate cache on miss) + run: uv sync --extra dev --dev + # Looking for e2e tests find-tests: + needs: prepare-deps runs-on: ubuntu-latest if: ${{ github.event.pull_request.draft == false }} outputs: @@ -38,15 +73,17 @@ jobs: with: python-version: '3.11' - - name: Install deps for collection - run: | - python -m pip install --upgrade pip - pip install -e ".[dev]" - - - name: Install uv - uses: astral-sh/setup-uv@v4 - - - name: Install dependencies + - name: Cache uv and venv + uses: actions/cache@v4 + with: + path: | + ~/.cache/uv + .venv + key: uv-${{ runner.os }}-py3.11-${{ hashFiles('uv.lock', 'pyproject.toml') }} + restore-keys: | + uv-${{ runner.os }}-py3.11- + + - name: Install dependencies (faster if cache hit) run: uv sync --extra dev --dev - name: Find test files @@ -151,4 +188,5 @@ jobs: with: nodeid: ${{ matrix.nodeid }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} + python-versions: env.PY_VERSIONS_JSON) secrets: inherit From f32664207fb0e385ee3f1c54a4f6a2155dc9f1f4 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:28:38 -0700 Subject: [PATCH 158/177] env.PY_VERSION + us setup --- .github/workflows/e2e-subtensor-tests.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 991fb51c32..fab909e4a9 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ fromJson(env.PY_VERSIONS_JSON) }} + python-version: ${{ fromJson(env.PY_VERSION) }} steps: - uses: actions/checkout@v4 @@ -73,6 +73,11 @@ jobs: with: python-version: '3.11' + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: true + - name: Cache uv and venv uses: actions/cache@v4 with: @@ -188,5 +193,5 @@ jobs: with: nodeid: ${{ matrix.nodeid }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} - python-versions: env.PY_VERSIONS_JSON) + python-versions: env.PY_VERSION) secrets: inherit From 907aa2e95a41551c9c4813e3001239fd654c2004 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:36:10 -0700 Subject: [PATCH 159/177] env.PY_VERSION + us setup --- .github/workflows/e2e-subtensor-tests.yaml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index fab909e4a9..0ae5e9d802 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -18,19 +18,17 @@ on: default: "" env: - CARGO_TERM_COLOR: always - VERBOSE: ${{ github.event.inputs.verbose }} - PY_VERSION: '["3.11","3.12","3.13"]' + PY_VERSIONS: '["3.11","3.12","3.13"]' # job to run tests in parallel jobs: - + # prepare dependencies for all jobs ib cache prepare-deps: runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ${{ fromJson(env.PY_VERSION) }} + python-version: ${{ fromJson(env.PY_VERSIONS) }} steps: - uses: actions/checkout@v4 @@ -84,7 +82,7 @@ jobs: path: | ~/.cache/uv .venv - key: uv-${{ runner.os }}-py3.11-${{ hashFiles('uv.lock', 'pyproject.toml') }} + key: uv-${{ runner.os }}-py3.11-${{ hashFiles('pyproject.toml') }} restore-keys: | uv-${{ runner.os }}-py3.11- @@ -174,6 +172,7 @@ jobs: with: name: subtensor-localnet path: subtensor-localnet.tar + compression-level: 0 # 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 @@ -193,5 +192,5 @@ jobs: with: nodeid: ${{ matrix.nodeid }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} - python-versions: env.PY_VERSION) + python-versions: ${{ env.PY_VERSIONS }} secrets: inherit From 888c5cee77b804d01887ada4bbb8e2b4ea2c2699 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:45:04 -0700 Subject: [PATCH 160/177] update --- .github/workflows/e2e-subtensor-tests.yaml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 0ae5e9d802..fbb04ff0e8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -17,18 +17,26 @@ on: required: false default: "" -env: - PY_VERSIONS: '["3.11","3.12","3.13"]' - # job to run tests in parallel jobs: + # keep python version in one place + decide-versions: + runs-on: ubuntu-latest + outputs: + py_json: ${{ steps.set.outputs.py_json }} + steps: + - id: set + run: | + echo 'py_json=["3.11","3.12","3.13"]' >> "$GITHUB_OUTPUT" + # prepare dependencies for all jobs ib cache prepare-deps: + needs: decide-versions runs-on: ubuntu-latest strategy: fail-fast: false matrix: - python-version: ${{ fromJson(env.PY_VERSIONS) }} + python-version: ${{ fromJson(needs.decide-versions.outputs.py_json) }} steps: - uses: actions/checkout@v4 @@ -181,6 +189,7 @@ jobs: e2e-test: name: ${{ matrix.label }} needs: + - decide-versions - find-tests - pull-docker-image strategy: @@ -192,5 +201,5 @@ jobs: with: nodeid: ${{ matrix.nodeid }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} - python-versions: ${{ env.PY_VERSIONS }} + python-versions: ${{ needs.decide-versions.outputs.py_json }} secrets: inherit From 9c55c25f477ae71dd93f1ac7aa9fc893ff0827ff Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:51:29 -0700 Subject: [PATCH 161/177] prepare-deps --- .github/workflows/e2e-subtensor-tests.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index fbb04ff0e8..9e9fee5be0 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -27,7 +27,7 @@ jobs: steps: - id: set run: | - echo 'py_json=["3.11","3.12","3.13"]' >> "$GITHUB_OUTPUT" + echo 'py_json=["3.10", "3.11","3.12","3.13"]' >> "$GITHUB_OUTPUT" # prepare dependencies for all jobs ib cache prepare-deps: @@ -49,6 +49,7 @@ jobs: uses: astral-sh/setup-uv@v4 with: enable-cache: true + cache-dependency-glob: '**/pyproject.toml' - name: Cache uv and venv uses: actions/cache@v4 From b470c13838fd03f9b014f16df714ebc4dde3a981 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:58:43 -0700 Subject: [PATCH 162/177] find-tests --- .github/workflows/e2e-subtensor-tests.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 9e9fee5be0..4839229db8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -84,6 +84,7 @@ jobs: uses: astral-sh/setup-uv@v4 with: enable-cache: true + cache-dependency-glob: '**/pyproject.toml' - name: Cache uv and venv uses: actions/cache@v4 @@ -91,9 +92,9 @@ jobs: path: | ~/.cache/uv .venv - key: uv-${{ runner.os }}-py3.11-${{ hashFiles('pyproject.toml') }} + key: uv-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} restore-keys: | - uv-${{ runner.os }}-py3.11- + uv-${{ runner.os }}-py${{ matrix.python-version }}- - name: Install dependencies (faster if cache hit) run: uv sync --extra dev --dev From 8935c8c017efdb3acf3acc5dfa17e5d7c1713fc7 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 02:03:18 -0700 Subject: [PATCH 163/177] uv run pytest --- .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 4839229db8..992777b0b5 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -105,7 +105,7 @@ jobs: run: | set -euo pipefail test_matrix=$( - pytest -q --collect-only tests/e2e_tests \ + uv run pytest -q --collect-only tests/e2e_tests \ | sed -n '/^e2e_tests\//p' \ | sed 's|^|tests/|' \ | jq -R -s -c ' From 9a3a8484c8038881a6d2c505cb12a5978c2918fb Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 02:07:44 -0700 Subject: [PATCH 164/177] Install uv --- .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 76768340b2..90e86311c8 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -37,6 +37,7 @@ jobs: uses: astral-sh/setup-uv@v4 with: enable-cache: true + cache-dependency-glob: '**/pyproject.toml' - name: Cache uv and venv uses: actions/cache@v4 From 5cd3b50510ec341e5093132d08efb557d75d7213 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:05:52 -0700 Subject: [PATCH 165/177] prepare-deps and find-tests in parallel --- .github/workflows/e2e-subtensor-tests.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 992777b0b5..ae49368bbb 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -20,7 +20,7 @@ on: # job to run tests in parallel jobs: # keep python version in one place - decide-versions: + supported-versions: runs-on: ubuntu-latest outputs: py_json: ${{ steps.set.outputs.py_json }} @@ -31,7 +31,7 @@ jobs: # prepare dependencies for all jobs ib cache prepare-deps: - needs: decide-versions + needs: supported-versions runs-on: ubuntu-latest strategy: fail-fast: false @@ -191,7 +191,7 @@ jobs: e2e-test: name: ${{ matrix.label }} needs: - - decide-versions + - supported-versions - find-tests - pull-docker-image strategy: From 7968ef04d5e5dca3e4e3d4cb2ea66a952186dc28 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:10:26 -0700 Subject: [PATCH 166/177] links --- .github/workflows/e2e-subtensor-tests.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index ae49368bbb..5a5ecee1f9 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -36,7 +36,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ fromJson(needs.decide-versions.outputs.py_json) }} + python-version: ${{ fromJson(needs.supported-versions.outputs.py_json) }} steps: - uses: actions/checkout@v4 @@ -66,7 +66,6 @@ jobs: # Looking for e2e tests find-tests: - needs: prepare-deps runs-on: ubuntu-latest if: ${{ github.event.pull_request.draft == false }} outputs: @@ -203,5 +202,5 @@ jobs: with: nodeid: ${{ matrix.nodeid }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} - python-versions: ${{ needs.decide-versions.outputs.py_json }} + python-versions: ${{ needs.supported-versions.outputs.py_json }} secrets: inherit From 6d768bfbd211d5f8899966c921911f39f3528589 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:12:47 -0700 Subject: [PATCH 167/177] e2e-test <- prepare-deps --- .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 5a5ecee1f9..98efa6bab2 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -190,7 +190,7 @@ jobs: e2e-test: name: ${{ matrix.label }} needs: - - supported-versions + - prepare-deps - find-tests - pull-docker-image strategy: From c67c22238996549cf78ffb6318a79decfc139f0f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:15:36 -0700 Subject: [PATCH 168/177] e2e-test <- (prepare-deps, supported-versions) --- .github/workflows/e2e-subtensor-tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 98efa6bab2..e7cda9901b 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -190,6 +190,7 @@ jobs: e2e-test: name: ${{ matrix.label }} needs: + - supported-versions - prepare-deps - find-tests - pull-docker-image From 523ace50f83ca4ae36a0982c9e4d34fb1892173d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:30:22 -0700 Subject: [PATCH 169/177] try more power --- .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 e7cda9901b..0e11be05af 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -196,7 +196,7 @@ jobs: - pull-docker-image strategy: fail-fast: false - max-parallel: 16 + max-parallel: 32 matrix: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} uses: ./.github/workflows/_run-e2e-single.yaml From e3e5a55b9ff2bed768e983e2365acb758a06ff08 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:36:44 -0700 Subject: [PATCH 170/177] try ignor GH cache warnings + back 32 parallel --- .github/workflows/_run-e2e-single.yaml | 1 + .github/workflows/e2e-subtensor-tests.yaml | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 90e86311c8..893cc15c79 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -38,6 +38,7 @@ jobs: with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' + ignore-nothing-to-cache: true - name: Cache uv and venv uses: actions/cache@v4 diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 0e11be05af..7d71ab7cee 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -50,6 +50,7 @@ jobs: with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' + ignore-nothing-to-cache: true - name: Cache uv and venv uses: actions/cache@v4 @@ -84,6 +85,7 @@ jobs: with: enable-cache: true cache-dependency-glob: '**/pyproject.toml' + ignore-nothing-to-cache: true - name: Cache uv and venv uses: actions/cache@v4 @@ -196,7 +198,7 @@ jobs: - pull-docker-image strategy: fail-fast: false - max-parallel: 32 + max-parallel: 24 matrix: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} uses: ./.github/workflows/_run-e2e-single.yaml From 16a085c3511c6bd72020b7ae7a4683bdad60ed59 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:41:49 -0700 Subject: [PATCH 171/177] max-parallel: 16 --- .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 7d71ab7cee..f30fb2bdad 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -198,7 +198,7 @@ jobs: - pull-docker-image strategy: fail-fast: false - max-parallel: 24 + max-parallel: 16 matrix: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} uses: ./.github/workflows/_run-e2e-single.yaml From a18d15e33f300e3b77ec2676c4f00c1ad14d4824 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 11:19:49 -0700 Subject: [PATCH 172/177] Flake8 and Mypy - linters check --- .github/workflows/flake8-and-mypy.yml | 44 ++++++++++++--------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/.github/workflows/flake8-and-mypy.yml b/.github/workflows/flake8-and-mypy.yml index db0b0087d8..a53924b3d2 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 @@ -26,31 +26,25 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: Cache venv - id: cache + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: false + + - name: Cache uv and .venv uses: actions/cache@v4 with: - path: venv - key: | - v3-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} - restore-keys: | - v3-${{ runner.os }}-${{ runner.arch }}-${{ matrix.python-version }}- - - - name: Install deps (flake8 + mypy + project.dev) - if: ${{ steps.cache.outputs.cache-hit != 'true' }} - run: | - python -m venv venv - source venv/bin/activate - python -m pip install --upgrade pip - python -m pip install uv==0.8.14 - python -m uv sync --extra dev --active + path: | + ~/.cache/uv + .venv + key: uv-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + restore-keys: uv-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}- + + - name: Sync dev deps + run: uv sync --extra dev --dev - name: Flake8 - run: | - source venv/bin/activate - python -m flake8 bittensor/ --count - - - name: mypy - run: | - source venv/bin/activate - python -m mypy --ignore-missing-imports bittensor/ + run: uv run flake8 bittensor/ --count + + - name: Mypy + run: uv run mypy --ignore-missing-imports bittensor/ \ No newline at end of file From 37163713fb43c5e20469ac5c744c8c73ad720e11 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 11:36:36 -0700 Subject: [PATCH 173/177] update mypy and unit tests workflows + use GH vars --- .github/workflows/flake8-and-mypy.yml | 2 +- .../workflows/unit-and-integration-tests.yml | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/flake8-and-mypy.yml b/.github/workflows/flake8-and-mypy.yml index a53924b3d2..8f30259c04 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: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} steps: - name: Checkout repository diff --git a/.github/workflows/unit-and-integration-tests.yml b/.github/workflows/unit-and-integration-tests.yml index c5578108aa..3e2338facd 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.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} steps: - name: Checkout repository @@ -24,36 +24,36 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: "3.11" + python-version: ${{ matrix.python-version }} - - name: Cache venv - id: cache + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + enable-cache: false + + - name: Cache uv and .venv uses: actions/cache@v4 with: - path: venv - key: v2-${{ runner.os }}-${{ hashFiles('pyproject.toml') }} + path: | + ~/.cache/uv + .venv + key: uv-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + restore-keys: | + uv-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}- - - name: Install deps - if: ${{ steps.cache.outputs.cache-hit != 'true' }} - run: | - python -m venv venv - source venv/bin/activate - python -m pip install --upgrade pip - python -m pip install uv>=0.8.14 - python -m uv sync --extra dev --active + - name: Sync dev deps (idempotent; fast on cache hit) + run: uv sync --extra dev --dev - name: Unit tests timeout-minutes: 20 env: PYTHONUNBUFFERED: "1" run: | - source venv/bin/activate - python -m uv run pytest -n 2 tests/unit_tests/ --reruns 3 + uv run pytest -n 2 tests/unit_tests/ --reruns 3 - name: Integration tests timeout-minutes: 20 env: PYTHONUNBUFFERED: "1" run: | - source venv/bin/activate - python -m uv run pytest -n 2 tests/integration_tests/ --reruns 3 \ No newline at end of file + uv run pytest -n 2 tests/integration_tests/ --reruns 3 \ No newline at end of file From 5a3f9429ac34055fa7288f618b380debbe268b1d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 11:55:50 -0700 Subject: [PATCH 174/177] Using shared cache and vars.SDK_SUPPORTED_PYTHON_VERSIONS to pass python versions (Settings->Settings->Secret and variables->Actions->Repository variables) --- .github/workflows/_run-e2e-single.yaml | 12 +--- .github/workflows/compatibility.yml | 2 +- .github/workflows/e2e-subtensor-tests.yaml | 58 ++----------------- .../monitor_requirements_size_master.yml | 3 +- .../nightly-e2e-tests-subtensor-main.yml | 8 +-- 5 files changed, 13 insertions(+), 70 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 893cc15c79..60bf153065 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -9,9 +9,6 @@ on: image-name: required: true type: string - python-versions: - required: true - type: string jobs: run-e2e: @@ -22,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ${{ fromJson(inputs.python-versions) }} + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} steps: - name: Check-out repository @@ -36,9 +33,7 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 with: - enable-cache: true - cache-dependency-glob: '**/pyproject.toml' - ignore-nothing-to-cache: true + enable-cache: false - name: Cache uv and venv uses: actions/cache@v4 @@ -47,8 +42,7 @@ jobs: ~/.cache/uv .venv key: uv-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} - restore-keys: | - uv-${{ runner.os }}-py${{ matrix.python-version }}- + restore-keys: uv-${{ runner.os }}-py${{ matrix.python-version }}- - name: Install dependencies run: uv sync --extra dev --dev diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml index 88701bf246..af001a32ee 100644 --- a/.github/workflows/compatibility.yml +++ b/.github/workflows/compatibility.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index f30fb2bdad..dc9d20dba8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -19,52 +19,6 @@ on: # job to run tests in parallel jobs: - # keep python version in one place - supported-versions: - runs-on: ubuntu-latest - outputs: - py_json: ${{ steps.set.outputs.py_json }} - steps: - - id: set - run: | - echo 'py_json=["3.10", "3.11","3.12","3.13"]' >> "$GITHUB_OUTPUT" - - # prepare dependencies for all jobs ib cache - prepare-deps: - needs: supported-versions - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ${{ fromJson(needs.supported-versions.outputs.py_json) }} - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - - name: Install uv - uses: astral-sh/setup-uv@v4 - with: - enable-cache: true - cache-dependency-glob: '**/pyproject.toml' - ignore-nothing-to-cache: true - - - name: Cache uv and venv - uses: actions/cache@v4 - with: - path: | - ~/.cache/uv - .venv - key: uv-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} - restore-keys: | - uv-${{ runner.os }}-py${{ matrix.python-version }}- - - - name: Sync deps (populate cache on miss) - run: uv sync --extra dev --dev - # Looking for e2e tests find-tests: runs-on: ubuntu-latest @@ -78,12 +32,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.10' - name: Install uv uses: astral-sh/setup-uv@v4 with: - enable-cache: true + enable-cache: false cache-dependency-glob: '**/pyproject.toml' ignore-nothing-to-cache: true @@ -93,9 +47,8 @@ jobs: path: | ~/.cache/uv .venv - key: uv-${{ runner.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} - restore-keys: | - uv-${{ runner.os }}-py${{ matrix.python-version }}- + key: uv-${{ runner.os }}-py3.10-${{ hashFiles('pyproject.toml') }} + restore-keys: uv-${{ runner.os }}-py3.10- - name: Install dependencies (faster if cache hit) run: uv sync --extra dev --dev @@ -192,8 +145,6 @@ jobs: e2e-test: name: ${{ matrix.label }} needs: - - supported-versions - - prepare-deps - find-tests - pull-docker-image strategy: @@ -205,5 +156,4 @@ jobs: with: nodeid: ${{ matrix.nodeid }} image-name: ${{ needs.pull-docker-image.outputs.image-name }} - python-versions: ${{ needs.supported-versions.outputs.py_json }} secrets: inherit diff --git a/.github/workflows/monitor_requirements_size_master.yml b/.github/workflows/monitor_requirements_size_master.yml index 459e02c776..8315c8190a 100644 --- a/.github/workflows/monitor_requirements_size_master.yml +++ b/.github/workflows/monitor_requirements_size_master.yml @@ -19,9 +19,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} outputs: - py39: ${{ steps.set-output.outputs.py39 }} py310: ${{ steps.set-output.outputs.py310 }} py311: ${{ steps.set-output.outputs.py311 }} py312: ${{ steps.set-output.outputs.py312 }} diff --git a/.github/workflows/nightly-e2e-tests-subtensor-main.yml b/.github/workflows/nightly-e2e-tests-subtensor-main.yml index 0ab9f006ca..49b914871a 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.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} 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.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} 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.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} 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.10", "3.11", "3.12", "3.13"] + python-version: ${{ fromJson(vars.SDK_SUPPORTED_PYTHON_VERSIONS) }} steps: - name: Check-out repository From 3898f173393eeff247c658ba90c28ff7ec90343f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 12:04:15 -0700 Subject: [PATCH 175/177] try 8 * 4 max-parallel --- .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 dc9d20dba8..ca4e372f4f 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -149,7 +149,7 @@ jobs: - pull-docker-image strategy: fail-fast: false - max-parallel: 16 + max-parallel: 8 matrix: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} uses: ./.github/workflows/_run-e2e-single.yaml From e11a7e0aefba40f3d2c691f052f3611cd087affb Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 12:11:56 -0700 Subject: [PATCH 176/177] 16 * 4 max-parallel back --- .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 ca4e372f4f..dc9d20dba8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -149,7 +149,7 @@ jobs: - pull-docker-image strategy: fail-fast: false - max-parallel: 8 + max-parallel: 16 matrix: include: ${{ fromJson(needs.find-tests.outputs.test-files) }} uses: ./.github/workflows/_run-e2e-single.yaml From c17913db681517a1231480845dabf18cdd6224e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=B5?= Date: Mon, 8 Sep 2025 00:49:45 +0200 Subject: [PATCH 177/177] WIP: runtime browser --- bittensor/__init__.py | 2 + bittensor/utils/runtime_async_browser.py | 322 ++++++++++++++++++ bittensor/utils/runtime_browser.py | 304 +++++++++++++++++ bittensor/utils/test_runtime_async_browser.py | 113 ++++++ bittensor/utils/test_runtime_browser.py | 92 +++++ 5 files changed, 833 insertions(+) create mode 100644 bittensor/utils/runtime_async_browser.py create mode 100644 bittensor/utils/runtime_browser.py create mode 100755 bittensor/utils/test_runtime_async_browser.py create mode 100755 bittensor/utils/test_runtime_browser.py diff --git a/bittensor/__init__.py b/bittensor/__init__.py index 57b009ece9..14b3b447c4 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -3,6 +3,8 @@ from .core.settings import __version__, version_split, DEFAULTS, DEFAULT_NETWORK from .utils.btlogging import logging from .utils.easy_imports import * +from .utils.runtime_browser import runtime_browser as runtime +from .utils.runtime_async_browser import runtime_async_browser as async_runtime def __getattr__(name): diff --git a/bittensor/utils/runtime_async_browser.py b/bittensor/utils/runtime_async_browser.py new file mode 100644 index 0000000000..4abee8da42 --- /dev/null +++ b/bittensor/utils/runtime_async_browser.py @@ -0,0 +1,322 @@ +import asyncio +import bittensor as bt +from .btlogging import logging + +# Default network in case user does not explicitly initialize runtime browser. +DEFAULT_NETWORK='local' + +TYPE='ty' # depending on where we get metadata from, we have 'ty' or 'type', TODO: settle on metadata source + +class runtime_async_browser_imp(type): + async_subtensor = None + pallets = None + apis = None + subtensor_metadata = None + block = None # block used to initialize runtime and get metadata + network = None + items = {} + + # Support init through bt.runtime(network=...,block=...) + def __call__(cls,**kwargs): + cls.init(**kwargs) + return cls + + def init(cls,network=None,async_subtensor=None,block=None): + cls.block = block + if async_subtensor: + if async_subtensor != cls.async_subtensor: + cls.async_subtensor = async_subtensor + cls.network = None + cls.items.clear() + else: + if not network: + logging.warning(f"Initializing runtime browser with default network {DEFAULT_NETWORK}, block={block}.") + network = DEFAULT_NETWORK + if network != cls.network: + cls.items.clear() + cls.network = network + cls.async_subtensor = bt.async_subtensor(network=network) + + def get_async_subtensor(cls): + if cls.async_subtensor is None: + cls.init() + return cls.async_subtensor + + async def init_metadata(cls): + async_subtensor = cls.get_async_subtensor() + async_substrate = async_subtensor.substrate + if cls.block: + block = cls.block + else: + block = await async_subtensor.block + block_hash = await async_substrate.get_block_hash(block) + rt = await async_substrate.init_runtime(block_hash=block_hash) + if 1: + cls.subtensor_metadata = rt.metadata_v15.value() + else: + # not sure if this is the most future proof way to go to get to the metadata + md_dict = substrate.metadata.value[1] + version = list(md_dict.keys())[0] + cls.subtensor_metadata = md_dict[version] + cls.subtensor_metadata['apis'] = rt.metadata_v15.value()['apis'] + + # blocking at first call + def get_metadata(cls): + if cls.subtensor_metadata is None: + raise Exception(f'Metadata not initialized; call await bittensor.async_runtime.init_metadata() before using async_runtime') + return cls.subtensor_metadata + + # blocking at first call + def get_pallets(cls): + if cls.pallets is None: + md = cls.get_metadata() + cls.pallets = {p.get('name','?'):p for p in md['pallets']} + return cls.pallets + + # blocking at first call + def get_apis(cls): + if cls.apis is None: + #cls.apis = list(bt.core.settings.TYPE_REGISTRY['runtime_api'].keys()) + md = cls.get_metadata() + cls.apis = [item['name'] for item in md['apis']] + return cls.apis + + def dir(cls): + pallets = cls.get_pallets() + apis = cls.get_apis() + return list(pallets.keys())+apis + + # We cannot make __getattr__ async, or the end user would have to await the + # module _and_ the item within the module, that is, we don't want this: + # await (await bt.async_runtime.SubtensorModule).NetworkLastRegistered() + # So necessarily the first invocation will be blocking, as it needs to + # fetch metadata. + def __getattr__(cls,item): + if item == '__super__': + return object + if not item[0].isalpha(): + try: + ret = cls.__super__.__getattr__(item) + except Exception as e: + raise + return ret + if item == 'metadata': + return cls.subtensor_metadata + if item in cls.get_pallets(): + if not item in cls.items: + cls.items[item] = subbrowser_module( + subbrowser=cls, + module=item, + metadata=cls.get_pallets()[item], + ) + return cls.items[item] + apis = {} + try: + apis = cls.get_apis() + except Exception as e: + logging.warning(f'Error fetching apis - this is an internal bittensor.runtime error') + + if item in apis: + if not item in cls.items: + cls.items[item] = subbrowser_api(subbrowser=cls,api=item) + else: + raise Exception(f"Pallet or API {item} not found; use bittensor.runtime.dir() to get valid options") + return cls.items[item] + + def __repr__(cls): + return f'' + +# this makes bittensor.runtime behave like an object, without prior instantiation +class runtime_async_browser(metaclass=runtime_async_browser_imp): + def __dir__(): + return bt.runtime.dir() + +# proxy for subtensor module, e.g. bt.runtime.SubtensorModule or bt.runtime.System +class subbrowser_module: + _subbrowser = None + _module = None + _objs = None + _storage_entries = None + _constants = None + _metadata = None + + def __init__(self,subbrowser=None,module=None,metadata=None): + self._subbrowser = subbrowser + self._module = module + self._objs = {} + self._metadata = metadata + self._storage_entries = {item.get('name','?'):item for item in metadata['storage']['entries']} + self._constants = {item.get('name','?'):item for item in metadata['constants']} + + def dir(self): + return list(self._constants.keys())+list(self._storage_entries.keys()) + + def __getattr__(self,name): + if not name in self._objs: + md = tp = None + if name in self._storage_entries: + md = self._storage_entries[name] + tp = 'storage' + elif name in self._constants: + md = self._constants[name] + tp = 'constant' + else: + msg = f'Storage entry or constant "{name}" not found; available are: ' + msg += 'storage entries '+', '.join(self._storage_entries.keys()) + msg += ' and constants '+', '.join(self._constants.keys()) + msg += '\nPossibly the entry exists on a different block height; explicitly initialize on a block using e.g. bt.runtime.init(block=4000000)' + # TODO: implement a delayed error object, so that you can call bt.runtime.SubtensorModule.SubnetLimit(block=4000000) after SubnetLimit does not exist anymore? + raise Exception(msg) + # It is a design decision to not return the value of plain types, but still return an + # object, that can be queried e.g. like bt.runtime.Timestamp.Now(), because it keeps + # the option open to add block=... kwarg. + # The alternative is to test 'Plain' in md[TYPE] and perform the query here. + self._objs[name] = subbrowser_objproxy( + subbrowser_module=self, + name=name, + metadata=md, + tp=tp, + ) + return self._objs[name] + + def __repr__(self): + return f'' + +# proxy for maps and singular elements, either constants or storage entries e.g. +# bt.runtime.Timestamp.Now (having no index) +# bt.runtime.SubtensorModule.LastAdjustmentBlock (having 1D index: netuid) +# bt.runtime.System.Account (having 1D index: coldkey_ss58) +# bt.runtime.Commitments.RateLimit (constant, no index) +# accept: +# obj() +# obj.query() +# obj[29] +# obj(29) +# obj.query(29) +# and for the call-like interfaces, kwargs: +# obj(block=12345) +# obj.query(block=12345) +# obj(29,block=12345) +# obj.query(29,block=12345) +class subbrowser_objproxy: + _subbrowser_module = None + _name = None + _metadata = None + _type = None + _n_indices = None + + def __init__(self,subbrowser_module=None,name=None,metadata=None,tp=None): + self._name = name + self._subbrowser_module = subbrowser_module + self._metadata = metadata + self._fullname = self._subbrowser_module._module+'.'+self._name + self._type = tp + self._n_indices = self._get_n_indices() + + def _get_n_indices(self): + if type(self._metadata[TYPE]) is not dict: + return 0 + if 'Plain' in self._metadata[TYPE]: + return 0 + if 'Map' in self._metadata[TYPE]: + mapinfo = self._metadata[TYPE]['Map'] + hashers = mapinfo.get('hashers',[]) + return len(hashers) + + async def _query(self,*args,**kwargs): + if len(args) != self._n_indices: + if self._n_indices == 0: + raise Exception(f'plain item {self._fullname} does not accept indices; use {self._fullname}()') + raise Exception(f'map {self._fullname} requires {self._n_indices} indices') + if type(self._metadata[TYPE]) is not dict: + pass + elif 'Plain' in self._metadata[TYPE]: + pass + elif 'Map' in self._metadata[TYPE]: + # Specifically ensure that we enfore ss58 indices, showing pretty errors. + mapinfo = self._metadata[TYPE]['Map'] + hashers = mapinfo.get('hashers',[]) + for i,h in enumerate(hashers): + if h in ('Blake2_128Concat'): # in Stake "Identity" is an ss58 addr, in Alpha and other maps, "Identity" is an integer. + if type(args[i]) != str or len(args[i]) != 48: + raise Exception(f'index {i} of {self._fullname} should be an ss58 address') + if self._type == 'storage': + ret = await self._subbrowser_module._subbrowser.async_subtensor.query_module( + module=self._subbrowser_module._module, + name=self._name, + params=args, + block=kwargs.get('block',self._subbrowser_module._subbrowser.block) + ) + # don't even try to understand the logic of what query_module() returns; might be scale obj, str or int + try: + return ret.value + except: + return ret + elif self._type == 'constant': + ret = await self._subbrowser_module._subbrowser.async_subtensor.query_constant( + module_name=self._subbrowser_module._module, + constant_name=self._name, + block=kwargs.get('block',self._subbrowser_module._subbrowser.block) + ) + try: + return ret.value + except: + return ret + + async def __call__(self,*args,**kwargs): + #logging.warning(f'objproxy call like access: {args} // {kwargs}') + return await self._query(*args,**kwargs) + + def __getitem__(self,args): + logging.warning(f'indexed querying of runtime objects is officially not async, this may break in the future; use object(index0,...) instead of object[index0,...]') + # TODO: test if it might still work anyway to return a coroutine; we're not performing del or assigning values anyway + if type(args) != tuple: args = (args,) + return self._query(*args) + + def __repr__(self): + if self._n_indices>0: + indices = ','.join(f'index{i}' for i in range(self._n_indices)) + return f'' + return f'' + +# proxy for subtensor api, e.g. in order to call: +# bt.runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost() +# bt.runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost(block=12345) +class subbrowser_api: + _subbrowser = None + _api = None + _calls = None + def __init__(self,subbrowser=None,api=None): + self._subbrowser = subbrowser + self._api = api + self._calls = {} + self._methods = None + md = self._subbrowser.get_metadata() + for item in md['apis']: + if item['name'] == api: + self._methods = [m['name'] for m in item['methods']] + if self._methods is None: + logging.warning(f'Failed to get methods for api {api} from metadata') + self._methods = [] + + def dir(self): + #return list(bt.core.settings.TYPE_REGISTRY['runtime_api'][self._api]["methods"].keys()) + return self._methods + + def __getattr__(self,call): + if not call in self._calls: + #methods = bt.core.settings.TYPE_REGISTRY["runtime_api"][self._api]["methods"] + if not call in self._methods: + raise Exception(f'API call {self._api}.{call} not found; available calls are {", ".join(self._methods)}') + async def query(*args,block=None): + return await self._subbrowser.async_subtensor.query_runtime_api( + self._api, + call, + args, + block=block or self._subbrowser.block, + ) + self._calls[call] = query + return self._calls[call] + + def __repr__(self): + return f'' diff --git a/bittensor/utils/runtime_browser.py b/bittensor/utils/runtime_browser.py new file mode 100644 index 0000000000..ea7adba1bb --- /dev/null +++ b/bittensor/utils/runtime_browser.py @@ -0,0 +1,304 @@ +import bittensor as bt +from .btlogging import logging + +# Default network in case user does not explicitly initialize runtime browser. +DEFAULT_NETWORK='local' + +TYPE='ty' # depending on where we get metadata from, we have 'ty' or 'type', TODO: settle on metadata source + +class runtime_browser_imp(type): + subtensor = None + pallets = None + apis = None + subtensor_metadata = None + block = None # block used to initialize runtime and get metadata + network = None + items = {} + + # Support init through bt.runtime(network=...,block=...) + def __call__(cls,**kwargs): + cls.init(**kwargs) + return cls + + def init(cls,network=None,subtensor=None,block=None): + cls.block = block + if subtensor: + if subtensor != cls.subtensor: + cls.subtensor = subtensor + cls.network = None + cls.items.clear() + else: + if not network: + logging.warning(f"Initializing runtime browser with default network {DEFAULT_NETWORK}, block={block}.") + network = DEFAULT_NETWORK + if network != cls.network: + cls.items.clear() + cls.network = network + cls.subtensor = bt.subtensor(network=network) + + def get_subtensor(cls): + if cls.subtensor is None: + cls.init() + return cls.subtensor + + def get_metadata(cls): + if cls.subtensor_metadata is None: + subtensor = cls.get_subtensor() + substrate = subtensor.substrate + block = cls.block or subtensor.block + block_hash = substrate.get_block_hash(block) + rt = substrate.init_runtime(block_hash=block_hash) + if 1: + cls.subtensor_metadata = rt.metadata_v15.value() + else: + # not sure if this is the most future proof way to go to get to the metadata + md_dict = substrate.metadata.value[1] + version = list(md_dict.keys())[0] + cls.subtensor_metadata = md_dict[version] + cls.subtensor_metadata['apis'] = rt.metadata_v15.value()['apis'] + return cls.subtensor_metadata + + def get_pallets(cls): + if cls.pallets is None: + md = cls.get_metadata() + cls.pallets = {p.get('name','?'):p for p in md['pallets']} + return cls.pallets + + def get_apis(cls): + if cls.apis is None: + #cls.apis = list(bt.core.settings.TYPE_REGISTRY['runtime_api'].keys()) + md = cls.get_metadata() + cls.apis = [item['name'] for item in md['apis']] + return cls.apis + + def dir(cls): + return list(cls.get_pallets().keys())+cls.get_apis() + + def __getattr__(cls,item): + if item == '__super__': + return object + if not item[0].isalpha(): + try: + ret = cls.__super__.__getattr__(item) + except Exception as e: + raise + return ret + if item == 'metadata': + return cls.subtensor_metadata + if item in cls.get_pallets(): + if not item in cls.items: + cls.items[item] = subbrowser_module( + subbrowser=cls, + module=item, + metadata=cls.get_pallets()[item], + ) + return cls.items[item] + apis = {} + try: + apis = cls.get_apis() + except Exception as e: + logging.warning(f'Error fetching apis - this is an internal bittensor.runtime error') + + if item in apis: + if not item in cls.items: + cls.items[item] = subbrowser_api(subbrowser=cls,api=item) + else: + raise Exception(f"Pallet or API {item} not found; use bittensor.runtime.dir() to get valid options") + return cls.items[item] + + def __repr__(cls): + return f'' + +# this makes bittensor.runtime behave like an object, without prior instantiation +class runtime_browser(metaclass=runtime_browser_imp): + def __dir__(): + return bt.runtime.dir() + +# proxy for subtensor module, e.g. bt.runtime.SubtensorModule or bt.runtime.System +class subbrowser_module: + _subbrowser = None + _module = None + _objs = None + _storage_entries = None + _constants = None + _metadata = None + + def __init__(self,subbrowser=None,module=None,metadata=None): + self._subbrowser = subbrowser + self._module = module + self._objs = {} + self._metadata = metadata + self._storage_entries = {item.get('name','?'):item for item in metadata['storage']['entries']} + self._constants = {item.get('name','?'):item for item in metadata['constants']} + + def dir(self): + return list(self._constants.keys())+list(self._storage_entries.keys()) + + def __getattr__(self,name): + if not name in self._objs: + md = tp = None + if name in self._storage_entries: + md = self._storage_entries[name] + tp = 'storage' + elif name in self._constants: + md = self._constants[name] + tp = 'constant' + else: + msg = f'Storage entry or constant "{name}" not found; available are: ' + msg += 'storage entries '+', '.join(self._storage_entries.keys()) + msg += ' and constants '+', '.join(self._constants.keys()) + msg += '\nPossibly the entry exists on a different block height; explicitly initialize on a block using e.g. bt.runtime.init(block=4000000)' + # TODO: implement a delayed error object, so that you can call bt.runtime.SubtensorModule.SubnetLimit(block=4000000) after SubnetLimit does not exist anymore? + raise Exception(msg) + # It is a design decision to not return the value of plain types, but still return an + # object, that can be queried e.g. like bt.runtime.Timestamp.Now(), because it keeps + # the option open to add block=... kwarg. + # The alternative is to test 'Plain' in md[TYPE] and perform the query here. + self._objs[name] = subbrowser_objproxy( + subbrowser_module=self, + name=name, + metadata=md, + tp=tp, + ) + return self._objs[name] + + def __repr__(self): + return f'' + +# proxy for maps and singular elements, either constants or storage entries e.g. +# bt.runtime.Timestamp.Now (having no index) +# bt.runtime.SubtensorModule.LastAdjustmentBlock (having 1D index: netuid) +# bt.runtime.System.Account (having 1D index: coldkey_ss58) +# bt.runtime.Commitments.RateLimit (constant, no index) +# accept: +# obj() +# obj.query() +# obj[29] +# obj(29) +# obj.query(29) +# and for the call-like interfaces, kwargs: +# obj(block=12345) +# obj.query(block=12345) +# obj(29,block=12345) +# obj.query(29,block=12345) +class subbrowser_objproxy: + _subbrowser_module = None + _name = None + _metadata = None + _type = None + _n_indices = None + + def __init__(self,subbrowser_module=None,name=None,metadata=None,tp=None): + self._name = name + self._subbrowser_module = subbrowser_module + self._metadata = metadata + self._fullname = self._subbrowser_module._module+'.'+self._name + self._type = tp + self._n_indices = self._get_n_indices() + + def _get_n_indices(self): + if type(self._metadata[TYPE]) is not dict: + return 0 + if 'Plain' in self._metadata[TYPE]: + return 0 + if 'Map' in self._metadata[TYPE]: + mapinfo = self._metadata[TYPE]['Map'] + hashers = mapinfo.get('hashers',[]) + return len(hashers) + + def _query(self,*args,**kwargs): + if len(args) != self._n_indices: + if self._n_indices == 0: + raise Exception(f'plain item {self._fullname} does not accept indices; use {self._fullname}()') + raise Exception(f'map {self._fullname} requires {self._n_indices} indices') + if type(self._metadata[TYPE]) is not dict: + pass + elif 'Plain' in self._metadata[TYPE]: + pass + elif 'Map' in self._metadata[TYPE]: + # Specifically ensure that we enfore ss58 indices, showing pretty errors. + mapinfo = self._metadata[TYPE]['Map'] + hashers = mapinfo.get('hashers',[]) + for i,h in enumerate(hashers): + if h in ('Blake2_128Concat'): # in Stake "Identity" is an ss58 addr, in Alpha and other maps, "Identity" is an integer. + if type(args[i]) != str or len(args[i]) != 48: + raise Exception(f'index {i} of {self._fullname} should be an ss58 address') + if self._type == 'storage': + b = kwargs.get('block',self._subbrowser_module._subbrowser.block) + ret = self._subbrowser_module._subbrowser.subtensor.query_module( + module=self._subbrowser_module._module, + name=self._name, + params=args, + block=b + ) + # don't even try to understand the logic of what query_module() returns; might be scale obj, str or int + try: + return ret.value + except: + return ret + elif self._type == 'constant': + ret = self._subbrowser_module._subbrowser.subtensor.query_constant( + module_name=self._subbrowser_module._module, + constant_name=self._name, + block=kwargs.get('block',self._subbrowser_module._subbrowser.block) + ).value + # don't even try to understand the logic of what query_module() returns; might be scale obj, str or int + try: + return ret.value + except: + return ret + + def __call__(self,*args,**kwargs): + return self._query(*args,**kwargs) + + def __getitem__(self,args): + if type(args) != tuple: args = (args,) + return self._query(*args) + + def __repr__(self): + if self._n_indices>0: + indices = ','.join(f'index{i}' for i in range(self._n_indices)) + return f'' + return f'' + +# proxy for subtensor api, e.g. in order to call: +# bt.runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost() +# bt.runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost(block=12345) +class subbrowser_api: + _subbrowser = None + _api = None + _calls = None + def __init__(self,subbrowser=None,api=None): + self._subbrowser = subbrowser + self._api = api + self._calls = {} + self._methods = None + md = self._subbrowser.get_metadata() + for item in md['apis']: + if item['name'] == api: + self._methods = [m['name'] for m in item['methods']] + if self._methods is None: + logging.warning(f'Failed to get methods for api {api} from metadata') + self._methods = [] + + def dir(self): + #return list(bt.core.settings.TYPE_REGISTRY['runtime_api'][self._api]["methods"].keys()) + return self._methods + + def __getattr__(self,call): + if not call in self._calls: + #methods = bt.core.settings.TYPE_REGISTRY["runtime_api"][self._api]["methods"] + if not call in self._methods: + raise Exception(f'API call {self._api}.{call} not found; available calls are {", ".join(self._methods)}') + def query(*args,block=None): + return self._subbrowser.subtensor.query_runtime_api( + self._api, + call, + args, + block=block or self._subbrowser.block, + ) + self._calls[call] = query + return self._calls[call] + + def __repr__(self): + return f'' diff --git a/bittensor/utils/test_runtime_async_browser.py b/bittensor/utils/test_runtime_async_browser.py new file mode 100755 index 0000000000..e76248ce7a --- /dev/null +++ b/bittensor/utils/test_runtime_async_browser.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python + +# testing code for runtime async browser + +import bittensor as bt +import asyncio +import sys + +calls = [l for l in ''' +# subnet registration info +await bt.async_runtime.SubtensorModule.NetworkLastRegistered() +await bt.async_runtime.SubtensorModule.NetworkLastLockCost() +await bt.async_runtime.SubtensorModule.NetworkImmunityPeriod() +await bt.async_runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost() +await bt.async_runtime.SubtensorModule.SubnetOwner[29] +await bt.async_runtime.SubtensorModule.SubnetLocked[29] +await bt.async_runtime.SubtensorModule.NetworkRegisteredAt[29] +await bt.async_runtime.SubtensorModule.NetworkLastLockCost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]) +await bt.async_runtime.SubtensorModule.NetworkLastLockCost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]-1) +await bt.async_runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]) +await bt.async_runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]-1) +# hotkey registration and commitment info +await bt.async_runtime.SubtensorModule.NetworkRegistrationAllowed[29] +await bt.async_runtime.SubtensorModule.Burn(9) +await bt.async_runtime.SubtensorModule.Burn(29) +await bt.async_runtime.SubtensorModule.BlockAtRegistration[29,5] +await bt.async_runtime.SubtensorModule.AdjustmentInterval[29] +await bt.async_runtime.SubtensorModule.LastAdjustmentBlock[29] +await bt.async_runtime.SubtensorModule.LastAdjustmentBlock(29,block=6000000) +await bt.async_runtime.SubtensorModule.ImmunityPeriod[29] +await bt.async_runtime.Commitments.LastCommitment[3, '5HTRBzc7CZvASdVZc7Fwzqnp1jXeP9z3KpoRKDZWhYSAksKq'] +await bt.async_runtime.Commitments.CommitmentOf[3, '5HTRBzc7CZvASdVZc7Fwzqnp1jXeP9z3KpoRKDZWhYSAksKq'] +await bt.async_runtime.Commitments.MaxFields() +await bt.async_runtime.SubtensorModule.Weights[0,1] +await bt.async_runtime.SubtensorModule.Weights(0,1) +await bt.async_runtime.SubtensorModule.Weights(0,1,block=6000000) +await bt.async_runtime.SubtensorModule.TotalHotkeyAlpha['5HmmmmhaoDbmt4KcYmAXETvfGBwFmcNgVCYSBH5JZRv6wrFf',29] +await bt.async_runtime.SubtensorModule.TotalHotkeyAlpha('5HmmmmhaoDbmt4KcYmAXETvfGBwFmcNgVCYSBH5JZRv6wrFf',29,block=6000000) +# various +await bt.async_runtime.Timestamp.Now() +await bt.async_runtime.Timestamp.Now(block=6000000) +await bt.async_runtime.Timestamp.Now(block=5900000) +(await bt.async_runtime.SubtensorModule.Alpha['5HmmmmhaoDbmt4KcYmAXETvfGBwFmcNgVCYSBH5JZRv6wrFf','5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn',29])['bits']/(1<<64) +(await bt.async_runtime.SubtensorModule.Alpha['5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn','5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn',29])['bits']/(1<<64) +await bt.async_runtime.System.Account['5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn'] +await bt.async_runtime.SubtensorModule.Owner['5GuNCPiUQBVoXCJgrMeXmc7rVjwT5zUEK5RzjhGmVqbQA5me'] +await bt.async_runtime.Proxy.Proxies['5Enrkn6rRLGz4tK3TWamnbKYECKpz3hffNk9RBaDC426jLgb'] +# init runtime interface with particular fixed block: +await bt.async_runtime.System.Number() +await bt.async_runtime(block=6000000).System.Number() +await bt.async_runtime.System.Number() +await bt.async_runtime(block=None).System.Number() +# init runtime interface with particular network: +await bt.async_runtime(network='test').System.Number() + +# exceptions, containing usage help: +await bt.async_runtime.NonExistantPallet[0] +await bt.async_runtime.System.NonExistentItem[0] +await bt.async_runtime.SubnetRegistrationRuntimeApi.no_such_call() +await bt.async_runtime.Timestamp.Now[1] +await bt.async_runtime.SubtensorModule.AdjustmentInterval[0,0] +await bt.async_runtime.System.Account() +await bt.async_runtime.System.Account[0] +await bt.async_runtime.System.Account['abc'] +await bt.async_runtime.System.Account['5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn',0] + +# exposed metadata, lists and __repr__ containing usage help: +bt.async_runtime +bt.async_runtime.dir() +bt.async_runtime.SubnetRegistrationRuntimeApi.dir() +bt.async_runtime.Commitments.dir() +bt.async_runtime.Commitments._metadata +bt.async_runtime.Commitments.LastCommitment._metadata +bt.async_runtime.Commitments +bt.async_runtime.Commitments.LastCommitment +bt.async_runtime.Commitments.MaxFields +'''.split('\n')] + +if len(sys.argv)>1: + block = int(sys.argv[1]) + bt.async_runtime.init(block=block) + +async def tests(calls): + # async_runtime requires explicit init call, or we'd have to await + # bt.async_runtime.SubtensorModule before we could use its properties. + await bt.async_runtime.init_metadata() + + # small demonstration of typical async usage with gather, matches first + # three test cases: + res3 = await asyncio.gather( + bt.async_runtime.SubtensorModule.NetworkLastRegistered(), + bt.async_runtime.SubtensorModule.NetworkLastLockCost(), + bt.async_runtime.SubtensorModule.NetworkImmunityPeriod() + ) + print('gather result:',res3) + + for call in calls: + if len(call) == 0 or call[0] == '#': + print(call) + continue + print(f'evaluating: {call}') + try: + if 'await' in call: + exec(f'async def fn():\n return {call}') + result = await locals()['fn']() + else: + result = eval(call) + print(f'--> {result}') + except Exception as e: + print(f'--> exception: {e}') + print() + +asyncio.run(tests(calls)) diff --git a/bittensor/utils/test_runtime_browser.py b/bittensor/utils/test_runtime_browser.py new file mode 100755 index 0000000000..a94caa9b41 --- /dev/null +++ b/bittensor/utils/test_runtime_browser.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python + +# testing code for runtime browser + +import bittensor as bt +import sys + +calls = [l for l in ''' +# subnet registration info +bt.runtime.SubtensorModule.NetworkLastRegistered() +bt.runtime.SubtensorModule.NetworkLastLockCost() +bt.runtime.SubtensorModule.NetworkImmunityPeriod() +bt.runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost() +bt.runtime.SubtensorModule.SubnetOwner[29] +bt.runtime.SubtensorModule.SubnetLocked[29] +bt.runtime.SubtensorModule.NetworkRegisteredAt[29] +bt.runtime.SubtensorModule.NetworkLastLockCost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]) +bt.runtime.SubtensorModule.NetworkLastLockCost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]-1) +bt.runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]) +bt.runtime.SubnetRegistrationRuntimeApi.get_network_registration_cost(block=bt.runtime.SubtensorModule.NetworkRegisteredAt[29]-1) +# hotkey registration and commitment info +bt.runtime.SubtensorModule.NetworkRegistrationAllowed[29] +bt.runtime.SubtensorModule.Burn(9) +bt.runtime.SubtensorModule.Burn(29) +bt.runtime.SubtensorModule.BlockAtRegistration[29,5] +bt.runtime.SubtensorModule.AdjustmentInterval[29] +bt.runtime.SubtensorModule.LastAdjustmentBlock[29] +bt.runtime.SubtensorModule.LastAdjustmentBlock(29,block=6000000) +bt.runtime.SubtensorModule.ImmunityPeriod[29] +bt.runtime.Commitments.LastCommitment[3, '5HTRBzc7CZvASdVZc7Fwzqnp1jXeP9z3KpoRKDZWhYSAksKq'] +bt.runtime.Commitments.CommitmentOf[3, '5HTRBzc7CZvASdVZc7Fwzqnp1jXeP9z3KpoRKDZWhYSAksKq'] +bt.runtime.Commitments.MaxFields() +bt.runtime.SubtensorModule.Weights[0,1] +bt.runtime.SubtensorModule.Weights(0,1) +bt.runtime.SubtensorModule.Weights(0,1,block=6000000) +bt.runtime.SubtensorModule.TotalHotkeyAlpha['5HmmmmhaoDbmt4KcYmAXETvfGBwFmcNgVCYSBH5JZRv6wrFf',29] +bt.runtime.SubtensorModule.TotalHotkeyAlpha('5HmmmmhaoDbmt4KcYmAXETvfGBwFmcNgVCYSBH5JZRv6wrFf',29,block=6000000) +# various +bt.runtime.Timestamp.Now() +bt.runtime.Timestamp.Now(block=6000000) +bt.runtime.Timestamp.Now(block=5900000) +bt.runtime.SubtensorModule.Alpha['5HmmmmhaoDbmt4KcYmAXETvfGBwFmcNgVCYSBH5JZRv6wrFf','5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn',29]['bits']/(1<<64) +bt.runtime.SubtensorModule.Alpha['5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn','5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn',29]['bits']/(1<<64) +bt.runtime.System.Account['5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn'] +bt.runtime.SubtensorModule.Owner['5GuNCPiUQBVoXCJgrMeXmc7rVjwT5zUEK5RzjhGmVqbQA5me'] +bt.runtime.Proxy.Proxies['5Enrkn6rRLGz4tK3TWamnbKYECKpz3hffNk9RBaDC426jLgb'] +# init runtime interface with particular fixed block: +bt.runtime.System.Number() +bt.runtime(block=6000000).System.Number() +bt.runtime.System.Number() +bt.runtime(block=None).System.Number() +# init runtime interface with particular network: +bt.runtime(network='test').System.Number() + +# exceptions, containing usage help: +bt.runtime.NonExistantPallet[0] +bt.runtime.System.NonExistentItem[0] +bt.runtime.SubnetRegistrationRuntimeApi.no_such_call() +bt.runtime.Timestamp.Now[1] +bt.runtime.SubtensorModule.AdjustmentInterval[0,0] +bt.runtime.System.Account() +bt.runtime.System.Account[0] +bt.runtime.System.Account['abc'] +bt.runtime.System.Account['5HHHHHzgLnYRvnKkHd45cRUDMHXTSwx7MjUzxBrKbY4JfZWn',0] + +# exposed metadata, lists and __repr__ containing usage help: +bt.runtime +bt.runtime.dir() +bt.runtime.SubnetRegistrationRuntimeApi.dir() +bt.runtime.Commitments.dir() +bt.runtime.Commitments._metadata +bt.runtime.Commitments.LastCommitment._metadata +bt.runtime.Commitments +bt.runtime.Commitments.LastCommitment +bt.runtime.Commitments.MaxFields +'''.split('\n')] + +if len(sys.argv)>1: + block = int(sys.argv[1]) + bt.runtime.init(block=block) + +for call in calls: + if len(call) == 0 or call[0] == '#': + print(call) + continue + print(f'evaluating: {call}') + try: + result = eval(call) + print(f'--> {result}') + except Exception as e: + print(f'--> exception: {e}') + print()